Skip to content

Scrap and build v0.1.0 - #3

Merged
yukikurage merged 210 commits into
mainfrom
scrap-and-build-v0.1.0
Jul 4, 2026
Merged

Scrap and build v0.1.0#3
yukikurage merged 210 commits into
mainfrom
scrap-and-build-v0.1.0

Conversation

@yukikurage

Copy link
Copy Markdown
Member

No description provided.

yukikurage and others added 30 commits June 8, 2026 17:31
…lth check endpoints

- Add Drizzle ORM configuration for PostgreSQL database.
- Create user role enum and users table schema.
- Implement application structure with Hono framework for routing.
- Add middleware for error handling, request context, and not found responses.
- Develop user repository, service, and routes for CRUD operations.
- Implement health check route to monitor server status.
- Set up environment variable validation and configuration loading.
- Create logging utility for structured logging.
- Add TypeScript configuration and build setup with tsdown.
- Include integration tests for user API endpoints.
…urce span tracking

- Introduced `LocalVarId` and `VariableResolution` types in `Katari.Data.Id` for local variable identification and resolution.
- Added `TypeResolution` data type to represent various type resolutions including data, requests, and generics.
- Refactored `NormalizedType` to use `NormalizedBaseType` instead of `NormalizedTypeBase`, enhancing clarity in type representation.
- Implemented `intersectType` and `intersectBaseType` functions for type intersection logic in `Katari.Data.NormalizedType`.
- Created `SourceSpan` and `Position` types in a new `Katari.Data.SourceSpan` module to track source code positions and spans.
- Added JSON serialization for `Position` and `SourceSpan` types.
- Implemented utility functions for checking if a position is contained within a source span.
Phase-indexed AST (Katari.Data.AST) を仕様変更込みで完成させ、normalizer
を含む型システム層を多角レビューに基づき修正。

AST:
- agent 引数 `label => pattern` / request・external・primitive・data 引数
  `label : type ?= default` / variable pattern のデフォルト引数
- spread 全廃、`agent type -> type`、matcher / request handler の generics
- 単一 Located/Reference 系、retag ヘルパ一式、deriving stock 統一

型システム (Normalizer / Environment / Error):
- generic 上限の既定を kind の top に(未登録 generic の空虚な subtype を防止)
- effect override / bound 吸収の shadow セマンティクスを修正(guarantee 保持)
- generic 引数の arity 検査 K3008 を normalize 境界に追加
- layer slot を Maybe + combineSlot/subtypeSlot に統一、object/sequence の
  整列を combine/subtype で共有
- Environment を GenericParameterInfo(kind 込み)に集約、並列 map を解消
- Error にソース span を導入(Located + checker 境界付与方針)、派生可能な
  message を構造から描画

基盤: lazy RWS→CPS、Common を Data.Map.Merge へ、命名 typo/略称の一掃、
CI lint job に Haskell ツールチェーン追加、stub 削除。

compiler 52/0・hlint clean・ビルド警告ゼロ。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Severity / severityOf(typeErrorCode と並ぶ per-error 総関数、catch-all
  なしで列挙し将来のエラー追加に severity 決定を強制)
- Diagnostics = Seq LocatedCompilerError(フェーズの writer monoid 想定)
- finalizeDiagnostics(dedup + source 位置順)/ renderDiagnostics
- emission ヘルパと module 分割は mtl が入る Identifier 着工時に行う方針
  (それまで Error は純カタログのまま)

compiler 54/0・hlint clean・ビルド警告ゼロ。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Katari.Diagnostics 新設: Error から Diagnostics 機構を移設し emission ヘルパ
  reportAt / capture / finalizeDiagnostics / renderDiagnostics を追加(mtl が
  入るのでここで分離、Error は純カタログに復帰)。LocatedCompilerError synonym 削除
- Katari.Identifier: モナド骨格のみ(env/state / RWS 別名 / runIdentifier /
  fresh-id 供給 / scope lookup・with)。解決ウォークは未実装
- Katari.Panic: 内部エラー(不変条件違反)用の abort チャネル panic。Error と別系統
- emission は reportAt 一本(投機的 report は削除、bridge 着工時に転送形を追加)
- tests: DiagnosticsSpec / IdentifierSpec / PanicSpec

compiler 59/0・hlint clean・ビルド警告ゼロ。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- freshGenericId / freshLocalVariableId を get-then-put から MonadState の
  state コンビネータ一発に(src 唯一の非イディオム箇所、解決ウォークの雛形)
- finalizeDiagnostics: ソートキーが Located 上で injective である不変条件を明記
- Error モジュールヘッダで Diagnostics(蓄積)/ Panic(内部中断)へ相互参照

レビュー結果: 正しさの欠陥ゼロ(capture / finalize / panic / fresh-id / scope
復元を実証検証)。compiler 59/0・hlint clean・警告ゼロ。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Katari.Data.Variant → Katari.Data.Variance に改名 (未使用 Polarity 削除)
- Phase コンストラクタ Zonked → Typed (型族4本 + コメント)
- 単一コンストラクタ record を data T = T {...} 通常構文へ統一
  (SemanticType/NormalizedType/Error の計17型; sum 型は GADT 維持)
- kind の stringly-typed 表現を既存 GenericKind enum に統一
  (kindOf/tellKindMismatch/substituteGenerics, renderGenericKind 追加)
- lint-fix scripts 追加 (hlint --refactor / biome --fix);
  一部 error-info を newtype 化

警告ゼロ (-Wall -Wcompat 他) ・compiler-test 59/59 green

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ricArgumentsを追加し、UseStatementを新規作成。型注釈を強化し、コメントを更新。
…on and attribute handling

- Enhanced the Identifier module to define the module interface and import context for name resolution.
- Introduced new data types for exported symbols and module interfaces.
- Updated the Normalizer module to refine attribute handling, ensuring attributes are not distributed into invariant data arguments.
- Improved subtype comparisons by incorporating a contextual "world" for attribute visibility.
- Adjusted tests to reflect changes in attribute handling and subtype relationships.
- Updated package.json scripts for more granular type checking and added CLAUDE.md for documentation.
…EffectRowにtailsフィールドを追加し、効果の正規化およびサブスティテューションのロジックを改善。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CompilerError を Parse(K1)/Identifier(K2)/Type(K3)/Lowering(K4) の phase 圏に拡張。Identifier 後に解決済み名が環境/宣言に無いのは不変条件違反なので UnknownData/Request/Generic(K3002-3004) を削除し Katari.Panic へ。dataInfoFor/requestInfoFor を非 Maybe 化。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Parser / Identifier(scan+identify) / Environment(global buildEnvironment) / Check / Lowering / Schema の I/O を stub で固定し Katari.Compile で結線。IR は Data.IR の placeholder。runtime が module 毎にアップロードするため link は無し。中身は後続で実装。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lexer/Type/Pattern/Expression/Parser に分割した recursive-descent parser。
中身は全 stub だった Katari.Parser を本実装に置換。

- 文境界でだけ改行を有意化 (旧 compiler のバーチャルセミコロン相当)。
  line/multiline の 2 space-consumer を Reader の spaceMode で切替、
  演算子トークンは常に改行を食う (行末演算子で継続 = Go ルール)。
- 区切り記号: 分配束縛 =>、値割当 =、戻り ->。関数型は agent kw 必須。
  generic upper-bound は [T extends Bound] (記号も予約語も増やさない)。
- use はブロック残りを継続 body として束縛 (collectBlockItems が再帰)。
- next/break は Reader の loopContext で for/handler 版に振り分け。
- 宣言レベル error recovery: 壊れた宣言を DeclarationError sentinel に
  置換し列 1 の宣言キーワードへ同期、診断は State に蓄積して全件返す。
- megaparsec label で declaration/expression/type/pattern のエラー改善。
- ParserSpec で hello/算術/data+match/for/handler/use/import/narrowing/
  generics/recovery 等を網羅 (91 examples green, ormolu/hlint クリーン)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Added `Katari.Identifier.Pattern` module for resolving patterns, including variable, wildcard, literal, tuple, type-filter, record, and constructor patterns.
- Introduced functions to resolve field patterns and constructor references.
- Created `Katari.Identifier.Type` module for resolving type-level syntax, including primitive types, agents, applications, tuples, unions, and objects.
- Implemented generic parameter handling and resolution within the type system.
- Updated the parser to correctly handle source spans for module names and type references.
- Enhanced the identifier tests to cover new functionalities, including diagnostics for undefined names and duplicate declarations.
- Improved symbol table visibility checks in the identifier tests.
- qualified-or-bare の名前解決を resolveQualifiedReference に共通化
  (type-name / constructor / request-handler の3重複を解消)
- 重複検出を declarationBindings から駆動し TopLevelName を削除
  (名前空間分類の二重定義・resolveModule の二重走査を解消)
- Identifier テストの診断 assertion を shouldBe に厳格化
- then 節の with-state / 値読みの正しい挙動を回帰テストで固定

挙動はリファクタ前と一致。compiler テスト 127/0 green。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…境を追加

Typechecker.Check を Katari.Typechecker に昇格し、Parser/Identifier と同じ
「トップ entry + 同名ディレクトリに helper」規約に揃える。

TypeEnvironment を 4 環境に拡張 (buildEnvironment 本体は引き続き stub):
- valueEnvironment: agent/external/primitive を「top-level の agent 型を
  持つ変数」として統一的に扱う (cross-module 参照を 1 lookup で型付け)
- synonymEnvironment: 型シノニムを env-builder が収集・展開・循環検出。
  kind-agnostic なので definition は NormalizedGenericArgument

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Introduced `ModuleName` utility functions for constructing module names from segments, extracting last segments, and checking module coverage.
- Enhanced `IdentifierError` to include a new error for reserved module names, with appropriate diagnostics.
- Updated the identifier resolution process to support desugaring of binary and unary operators into calls to the `primitive` module, ensuring operators are resolved without needing import context.
- Added a new `JSONSchema` representation for runtime schema requirements, including serialization to JSON.
- Created a `Primitive` module to define the mapping of operators to their corresponding primitive functions.
- Embedded the `primitive` module source into the compiler, ensuring operators are correctly desugared during the identification phase.
- Implemented tests for the standard library to verify the correct embedding and operator desugaring behavior.
- Introduced SQL schema for runtime components including tables for blobs, scopes, threads, delegations, escalations, external calls, instances, runs, and environment entries.
- Implemented TypeScript definitions for database tables and runtime engine types, including instances, threads, scopes, and value models.
- Established relationships between tables with foreign keys to maintain data integrity.
- Added JSON type definition for arbitrary JSON values used in runtime schemas and values.
- Updated type names in NormalizerSpec for clarity and consistency.
- Added tests for subtype relationships between tuples and arrays, ensuring correct handling of fixed-length and variable-length types.
- Enhanced object subtype tests to verify behavior with additional fields.
- Improved normalization and denormalization tests for semantic types, ensuring accurate round-tripping.
- Refactored database schema to remove the scope_variables table, integrating its functionality into the scopes table with a new values column.
- Updated foreign key constraints and added indexes for improved query performance.
- Removed health check routes and adjusted API surface to reflect current module structure.
- Enhanced TypeScript types for better clarity and added new default pattern matching functionality.
- Enabled strict TypeScript checks with noUncheckedIndexedAccess for improved type safety.
…lity

- Simplified the Normalizer module by removing the traverseArguments function and integrating its logic directly into the substituteType function. This change enhances readability and reduces complexity in handling argument traversal.
- Updated the substituteAttribute function to streamline the absorption of replaced generics, improving the overall structure and maintainability of the code.
- Fixed a bug in the referencesInExpression function within the ValueGraph module to ensure correct handling of record entries, enhancing the accuracy of expression reference analysis.
- Clarified comments in EnvironmentSpec to better explain the distinction between generic ids in different modules, ensuring clearer understanding of the variance fixed point mechanism.
yukikurage and others added 27 commits July 4, 2026 01:49
…escalation history

typescript/admin-web (@katari-lang/admin-web, Vite + React 19 + Tailwind 4
+ react-query): projects / dashboard / runs / run detail / agents /
agent invoke / escalations / snapshots / files / env / settings, against
the current runtime API. Semantic color tokens over CSS variables give
one-class theming (light / dark / system, no dark: twins); components
split atomic (ui/) vs domain (runs/, escalations/, agents/, values/,
schema-form/), padding-first spacing throughout.

Beyond main's console: an escalation inbox with a live sidebar badge and
in-place answering (schema-driven form, never-typed throws flagged
unanswerable), a run page that shows open questions inline plus the
answered Q&A transcript, re-run with the same argument, a snapshots page
with the head manifest, per-row reveal for non-secret env values, and a
ValueViewer fluent in the wire's $constructor / $ref (downloadable) /
$agent / $closure / $redacted shapes. Polling is condition-driven
(2.5 s while anything is live, quiet otherwise); a route-keyed error
boundary keeps the shell alive if a page throws.

runtime: GET /projects/:id/runs/:id/escalations serves the run's
answered-escalation transcript from run_escalations_audit (unseal +
redact like the run's own argument / result) — the only read the
console needed that the API did not yet expose. The delegation tree
stays out, as agreed, until the runtime-side work lands.

Verified live against the running runtime in headless Chrome: projects,
dashboard, runs, an errored run's typed-throw payload, agent invoke
form, snapshots, and env all render with real data; 199 runtime tests,
tsc, and biome are green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address the high-effort code review, plus a base-reactor refactor.

Correctness:
- Conform an external (FFI/http) call's RESULT against the callee's declared
  output schema, core-side in onDelegateAck (the one seam with the IR, the
  wrapper instance's schema, and its generics). A violation fails the run as a
  panic at the proxy, rather than a wrong-typed value corrupting a match later.
- Guard the client-JSON acceptance surfaces (run start, escalation answer) with
  decodeClientJson: a malformed decode is now a 400, not a 500.
- Bound prelude.array.range so an over-large (possibly AI-supplied) span can no
  longer stall or OOM the serial actor turn.
- onEscalate drops during the completion drain window (pendingOutcome set),
  symmetric with openInnerDelegation, so no relay is orphaned.

Cleanup / refactor:
- send(event, issuer) takes the issuing instance explicitly; applySendEdge is
  folded into send and the currentTurnOwner / turnInstance / turnOwnerId ambient
  is removed.
- Base reactor gains an issuedByCaller reverse index (the delegation-tree
  wiring): issuedDelegationsOf / hasIssuedDelegations / issuedRowOf are now
  O(children), not full-map scans; the ffi inner-call bridges get a parent index
  too.
- Shared isFailureRequest predicate (escalation-filter); the wire-codec
  constants + $-key escaping move to @katari-lang/types/wire.ts, imported by
  both the runtime codec and the FFI port (no more hand-mirrored lockstep).
- Port: narrow the aborted pre-check to the expected KatariCancelledError (a
  genuine cleanup failure now hits the diagnostic again); use node:crypto
  randomUUID.

Tests: runtime 199 (+ output-conform, range bound, undecodable-answer 400).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Panic becomes catchable via a special ambient clause, while staying unraisable.
The runtime needs no change (handleAsk already matches prelude.panic by string,
and panic already bubbles as a prelude.panic ask); the whole feature is in the
compiler front-end:

- Katari.Primitive: the wired-in panicRequestName (prelude.panic).
- Check.walkRequestHandler: recognize a bare `panic` clause structurally (it is
  undeclared, so it never resolves) and type it from a synthetic
  `panic(msg: string) -> never` signature.
- Check.checkHandlerScheme: EXCLUDE the panic clause from the continuation's
  overwrite effect row — this is what makes it ambient (addable to any handler),
  and is required: an undeclared request in an effect-row type would break
  variance lookups.
- Lowering.lowerRequestHandler: lower the panic clause to prelude.panic.
- Identifier.resolveRequestHandler: leave the bare `panic` reference unresolved
  without reporting an undefined-name error (undeclared by design).

Recovery is via `break` only (-> never, like throw). This also gives programs a
way to catch an interrupted-FFI panic and retry.

Tests: PanicSpec (5) + a LoweringSpec case (lowers to prelude.panic); compiler
533.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One run's live delegation tree: who summoned whom, right now. The read
side follows the escalation-list precedent — it reads the Layer 1 rows
(delegations + instances + kind extensions + escalations) directly, the
same durable mirror every reactor's warm issuedByCaller / handled
indexes are rebuilt from on recovery, so the API and the engine always
agree and no reactor is loaded just to look at a run.

- Edges compose as the base reactor documents them: callee instance =
  instances.delegation_id, children = delegations.caller_instance_id.
- Node labels from the kind extensions: core = target (agent name /
  closure), ffi = dispatch key; http keeps no request, so the node
  falls back to its reactor kind. ffi/http surface their own status
  (awaitingAnswer), which the envelope cannot express.
- Open escalations attach to their raiser, one row per bubble hop;
  `answerable` marks the api-addressed leg (the one the answer surface
  accepts), so the badged path in a UI leads to the waiting origin.
- Pure assembly (assembleDelegationTree) + project-scoped selects; a
  visited guard degrades corrupt rows to a truncated tree, never a
  hang. Terminal run => tree null (the rows are pure live routing).

Live-verified: interactive escalation run shows main → panel → two
parallel consult → ask chains with per-hop questions; answering both
completes the run and the tree returns to null.

Tests: delegation-tree (6) — runtime 211.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ri-lang/types

Run page: a live "Delegation tree" card while the run is in flight —
each node is one delegation edge with its instance's label (agent /
closure / external key, falling back to the reactor kind), a status
badge (running / cancelling / awaiting answer / pending while the
delegate is still in flight), and the open questions it raised; the
api-addressed leg is highlighted and relay hops are marked, so the
badged path leads to where the run is actually waiting. Polls with the
run and disappears on terminal (the rows are live routing).

Wire-convention catch-up: the runtime moved the value-wire constants +
$-key escaping into @katari-lang/types (wire.ts), shared with the
codec and the FFI port. The console now imports the same definition —
ValueViewer dispatches variants through wireKindOf and unescapes keys
with unescapeRecordKey, SchemaForm builds file/agent references from
FILE_KEY/AGENT_KEY/..., RunDetailPage checks REDACTED_KEY — so a
fourth hand-mirrored copy of the convention is gone.

Live-verified in headless Chrome against the running runtime: the
interactive playground run renders main → panel → two parallel consult
→ ask chains, with inline answering completing the run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…discord_bot

Five single-feature examples (agent-tools, ffi-hello, ffi-blob,
ffi-inner, ffi-throw) fold into examples/playground, one module per
concern, every entry live-verified against the running runtime:

- basics    — data/match, for (map + accumulator), parallel for,
              stateful inline handlers, prelude sampling
- tools     — agents as AI tools (schema derivation, typed JSON
              boundary, dynamic dispatch); the old agent-tools
- errors    — throw[T] raised + caught, and the ambient panic clause
              catching a live runtime failure (missing env entry)
- interactive — escalation: parallel consults each holding an open
              ask; the delegation-tree showcase
- ffi + ffi.ts — all four boundary shapes in one sidecar (plain
              values, file both directions, inner delegation, typed
              throw)

examples/discord_bot is main's bot ported to the current language:
match arrows / `=>` field patterns, `external agent` (no from-string),
`secret` -> `string of private`, typed throws (prelude.throw[T]) with
a union catch clause, env_not_found handler -> the ambient panic
clause, `session_provider` returning a handler -> inline use handler,
and the custom http_post sidecar replaced by a thin post_json over the
built-in http.fetch (one fewer FFI surface). Tool dispatch drops
tool_index for a metadata-name scan (variable patterns do not narrow
`T | null`). Compiles clean (18 modules) and bundles + deploys.

Adaptation notes: `io` has no surface spelling, so agents that perform
io leave their effect row to inference (an explicitly annotated row
cannot call an external today); a pure recursive group is annotated
with `prelude.throw[never]` as the empty upper bound.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ix workflows

Version model: haskell/cli/VERSION is a one-line file, the single
user-visible version of the CLI — `cliVersion` embeds it at build time
(file-embed, like the init templates) so `katari --version` and the
registry compiler-pin warning speak it; the cabal `version:` fields
stay X.Y.Z.0 placeholders (cabal cannot carry a pre-release suffix).
The pin comparison strips a pre-release suffix before taking the
release triple, so an rc CLI still matches a stable registry pin.

Scripts (versions-common.mjs shares the format + package enumeration):
- stamp-version  — rewrite VERSION + every haskell package.yaml +
  every typescript package.json to one version, before tagging.
- verify-versions — the release precheck: cross-check all of the
  above against the pushed tag; every workflow gates on it.
- stage-binary-packages / bump-versions — publish-time only: wrap the
  released binaries as @katari-lang/cli-<platform> and inject them
  into the shim's optionalDependencies (the committed tree stays
  installable before the first publish).

Workflows (tag-triggered, all gated on verify-versions):
- release-katari — build the CLI per platform (linux-x64,
  darwin-arm64), attach tarballs + sha256 to the GitHub Release.
- release-npm — on release-katari success: build the workspace,
  download the binaries, publish types/port/runtime/bundle, the
  platform packages, then the cli shim, under a dist-tag derived from
  the pre-release identifier. OIDC Trusted Publishing, no npm secret.
- release-katari-runtime — the server image to ghcr.io/<owner>/katari
  (amd64+arm64); :latest only moves on stable.
- release-vsix — package the editor extension and attach the .vsix.

Verified: stamp/verify round-trip both directions with zero residual
diff; stage+bump dry-run against local tarballs; katari --version
reports 0.1.0-rc6 from the embedded file; cli tests + hlint + ormolu
clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…node_modules/.bin

Setup friction, removed at its three sources:

- KATARI_BUNDLE_BIN: gone for dev. `katari apply` already resolved a
  project-local node_modules/.bin/katari-bundle by walking up from the
  project root, but ran whatever it found through `node` — correct for
  npm (a symlink to the JS entry) and a SyntaxError for pnpm (a POSIX
  launcher script). Canonicalize first: a JS target runs through node,
  anything else executes directly. The examples add @katari-lang/bundle
  as a devDependency — exactly what a user project would do — so the
  workspace install wires the bundler up by itself.

- Runtime env: the dev script loads .env files with node's own
  --env-file-if-exists (the built-in that replaced dotenv) — the repo's
  top-level .env first (shared with docker compose's substitution),
  then a package-local one, real environment winning over both. The
  loading is visible in the script line and ships nowhere: `start` and
  the container image take real environment only, and no env-file code
  exists in the app. A top-level .env.example documents every knob and
  the one-time key generation line.

- KATARI_API_URL: already unnecessary for the examples (katari.toml's
  [runtime].url) — the READMEs now say so instead of exporting it.

Live-verified end to end with zero exported variables: fresh default
database, `pnpm --filter @katari-lang/runtime dev` boots from the
top-level .env, `katari apply` + `katari run basics.main` both succeed
from a bare shell in examples/playground.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ro-setup dev

The runtime image now serves the admin console, local dev gets a real
S3 path, and the whole stack comes up with one command.

Console in the image:
- The runtime serves the built console at the root (/) alongside the
  JSON API (/api/v1) — one origin, no proxy. A new admin-web middleware
  serves the static dist with an /api guard and an index.html history
  fallback; when KATARI_ADMIN_WEB_DIST is unset (a source checkout) the
  root stays the JSON info and the console runs from its own vite dev
  server. The Dockerfile builds admin-web in the build stage and bakes
  its dist in, defaulting the env at it.
- The runtime is now a self-contained application bundle: tsdown inlines
  its one workspace dep (@katari-lang/types — types + the wire
  constants), so `pnpm deploy` never ships raw TypeScript the image
  cannot execute. It emits JS only (nothing imports its types), and
  @katari-lang/types drops to a devDependency.

S3 blob store for dev:
- compose.yaml gains an adobe/s3mock service (the maintained successor
  to MinIO OSS for local testing); the dockerized runtime points its
  blob store at it. S3BlobStore.ensureBucket creates the bucket on boot
  (idempotent, gated by BLOB_S3_CREATE_BUCKET) with a bounded retry so a
  just-started mock is tolerated — real AWS leaves it off.

Zero-setup dev:
- `pnpm run dev` brings up postgres + s3mock (compose) and runs the
  runtime + console in parallel (pnpm --parallel). The runtime reads the
  top-level .env via node's --env-file-if-exists. The dev .env.example
  is trimmed to the one required key plus the S3 pointer.

Self-host template:
- `katari init` scaffolds a compose.yaml wired to the published GHCR
  image (pinned to the CLI's version via a new {{version}} template
  placeholder) with Postgres + durable SeaweedFS (OSS, not the
  deprecated MinIO). Its env.example and README match.

Live-verified end to end: `docker compose up` → the console at
http://localhost:3000/ (assets + SPA deep-link fallback), /api/v1
health, and a file-blob round-trip (ffi.main → bytes=13) through
s3mock, all inside the built image; and the host `pnpm run dev` flow
with the same S3 path. runtime 205 tests + tsc + biome + ormolu clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…_API_KEY

The API is authenticated in v0.1.0. A caller sends `Authorization: Bearer
<KATARI_API_KEY>`; the runtime rejects anything else with 401.

- The key is separate from KATARI_SECRET_KEY (which only encrypts secrets
  at rest): KATARI_API_KEY is the auth token, KATARI_SECRET_KEY the
  at-rest cipher key. Both are required at boot — the runtime refuses to
  start without either, so an API is never accidentally left open and
  secrets are never left unencrypted.
- Auth is always on (no opt-out). The middleware exempts exactly two
  things, because neither can carry the header: /api/v1/health (the
  container healthcheck / uptime monitors) and every non-/api path (the
  console's own static assets — a browser can't attach Authorization to a
  <script>). The comparison is constant-time.
- The CLI already reads KATARI_API_KEY and sends it as a Bearer token, so
  it needed no change; a missing/wrong key now surfaces as a clean 401.
- The console gains a login gate: it renders normally and only interposes
  a login screen when a request comes back 401 (a query poll or a
  mutation), validates the entered key against a protected endpoint before
  letting the app back in, and stores it for the Bearer header. Settings
  gains sign-out (clear the key).
- `katari init` scaffolds a compose.yaml + .env.example that require
  KATARI_API_KEY (alongside the existing KATARI_SECRET_KEY); the dev
  compose + .env.example require it too. The CLI reads it from the
  environment, so the example READMEs export it from the repo `.env`.

Live-verified: runtime refuses to boot with no KATARI_API_KEY; with it
set, no-key/wrong-key requests 401 while the right key and /health pass;
the CLI works with the key and errors cleanly without; the built console
served by an auth-on runtime loads publicly and shows the login screen on
the first 401. runtime 212 tests (+auth, +api-key config) + tsc + biome +
ormolu clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Retarget the semantic color tokens to main's palette (highlight / katari / paper
+ danger / success / warning), so the whole console recolors from one place: cyan
`highlight` becomes the primary accent, neutrals become the warm paper / katari
scales, and each `-soft` tint is a translucent wash of its base.

Drop rounded corners and shadows everywhere (the Switch keeps its rounded-full
track, as on main), square + uppercase the Badge, and remove the top-bar and
sidebar separators to keep lines minimal.

Restore the missing logo: add public/katari.svg + favicon.svg, a Logo component
(masked glyph + wordmark + Console tag), and load fonts in index.html — the
KATARI wordmark is Montserrat, body is DM Sans, mono is DM Mono.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rowing

Add `io` (the external-call effect) and `pure` (the empty effect) as surface
type keywords alongside `all` / `never`, threaded through the AST, parser,
identifier resolution, query facts, and elaboration (SemanticEffectIo /
SemanticEffectPure), plus editor highlighting. An agent can now spell `with io`
or `with pure` directly instead of the `throw[never]` workaround.

Narrow a later match arm's variable / wildcard binder to the residual scrutinee
after earlier arms' covers are subtracted (`subtractCover`): `case null` then
`case rest` binds `rest` at the non-null residual — the in-language way to fold
a nullable. A refutable arm still sees the full scrutinee, so exhaustiveness and
every cover are unchanged; the residual only over-approximates, keeping it sound.

Adopt both in the discord_bot example (`with pure`, a null-narrowed `or_null`,
and `json.decode[unknown]` replacing the removed `jsonx.to_value`).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the hardcoded dev-default secret key from compose.yaml so the value comes
from the environment (a root `.env`), and gitignore `/.env` so that file never
lands in the repo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the pnpm `minimumReleaseAgeExclude` entry for hono@4.12.24; the pin is no
longer needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…DMIN_WEB_DIST

Whether the runtime serves the web console was a configured path
(KATARI_ADMIN_WEB_DIST) that only ever held one value — the image's
baked-in dist. Replace the knob with a fixed path next to the runtime
(`admin-web/dist`, relative to the working directory) and a presence
check: the image copies the console there, so it appears with no
configuration; a source checkout has no such directory, so the runtime
serves only the JSON API and the console runs from its vite dev server.
`mountAdminWeb` now returns whether it mounted, and the Dockerfile drops
the ENV.

Live-verified: the built image serves the console at / with no
KATARI_ADMIN_WEB_DIST in its environment (health public, the API still
401s without the key); a source-checkout runtime serves the JSON info
root instead. runtime 212 tests + tsc + biome clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`collectEntries` now returns each callable's whole `AgentBlock` (not just its `SchemaInfo`), so the
agent list / detail endpoints can carry the declaration's `@"..."` description alongside its schemas.
The IR already stamps `AgentBlock.description`, so this is pure read-path plumbing; `deriveAnswerSchema`
and the escalation answer-schema lookup are updated for the wider entry type.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The project `readme` column existed but nothing ever filled it, so the console's README card never
showed. Wire the pipeline end to end:

- runtime: add `PATCH /projects/:id` (`updateProjectSchema` / repository.update / service.update) that
  partially updates description + readme (null clears, omitted leaves untouched);
- cli: `katari apply` reads the project's `README.md` and sends it — creating a project with it, and
  updating an existing project's description + README on every deploy so they track the source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Design: adopt main's type / control language — Lexend (Tera) display face + DM Sans / DM Mono via the
font tokens, a global pointer cursor on clickables, `color-scheme` pinned per theme so native controls
(number stepper, <select> popup) render in the right palette, teal Lexend page titles, uppercase table
heads, and a chevron affordance on the (previously arrow-less) Select.

Restored / improved views:
- Agents: a collapsible folder tree with two-line rows showing each agent's description.
- Schema: a structured visualizer (type badges, left-border nesting, required / union / enum / array /
  reference / never) replacing the flat text dump.
- Dashboard: render the project README as themed markdown (MarkdownContent — inline element styles, no
  `@tailwindcss/typography` dependency).
- Snapshots: a per-row link into that snapshot's agent list.
- ValueViewer: nest straight down with a fixed indent (constructor tag as a header, fields below-left)
  instead of stepping right by each label / tag width.
- Escalations: optimistically drop an answered escalation from the inbox instead of waiting a poll.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…button

The agent detail page renders the callable's `@"..."` description (as themed markdown, inline code and
all) below the header, and the Invoke panel gets a Paste button that fills the argument form from a JSON
value on the clipboard.

Pasting revealed two stale-field bugs in the schema form, now fixed: the argument form remounts on paste
so fields with their own state (the raw-JSON buffer) re-read the pasted value, and a union field derives
its initial branch from the value (matching a constructor by its `$constructor` tag) instead of always
opening the first branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Appearance already lives in the top bar and the only other Settings function was changing the API key,
which logging out + the login screen covers — so drop the Settings page and route, and add a top-bar
user menu showing the runtime the console is signed in to plus a Log out action (clears the key and
re-arms the auth gate).

Give the top bar `relative z-40` so the menu isn't trapped under page content: its `backdrop-blur`
makes it a stacking context, so the menu's own z-index only ordered within the bar and page `<select>`s
painted over it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…f panicking

A missing secret is an anticipated configuration failure (an optional
integration, a default) that a program should be able to recover from, not a
broken invariant. Move it onto the typed-error channel: `get_secret` now throws
`env.missing_secret(key, message)` — the key rides along so a fallback handler
can branch on which secret is absent. Uncaught, the throw still fails the run,
so required-config behaviour is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ommit)

Files (api-root-owned blobs) had no reclamation path — the byte-reclaim
machinery already existed (ResourcePool.reclaimedBytes → the substrate deletes
the BlobStore bytes strictly after the commit), but nothing let a user free a
file. Add DELETE /projects/:id/files/:fileId on top of that machinery:
`pool.deleteBlobOwnedBy` frees only an api-root-owned blob (never an in-flight
FFI call's mid-call upload), staging its row deletion this turn and its bytes
for the post-commit reclaim. A run still holding the deleted file's ref reads
it as gone — the explicit delete is the user's call.

Wires the consumers to match: `katari file delete` (with id-prefix resolution)
and the admin console's Files page delete button. Drops the now-unused
NotImplementedError.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ete teardown

Three rc6-hardening changes to the runtime lifecycle that share the facade /
registry / actor seam:

- FFI blob side-channel auth: the sidecar's blob upload / download runs against
  the runtime's authenticated `/api`, but it sent no bearer and was never handed
  the key — every blob op 401'd on an auth-enforced (i.e. every real) runtime.
  The runtime now passes the API key to the sidecar as env and the port's blob
  client sends `Authorization: Bearer`.

- Boot-time run recovery: reactivation was lazy (a project reloaded only when
  next touched), so after a restart a long-running run stayed suspended until
  external traffic happened to arrive. `bin.ts` now calls
  `activateInFlightProjects` once the server is listening, resuming every
  project that holds a non-terminal run — fault-isolated per project.

- Project-delete teardown: deleting a project now evicts its warm actor (killing
  its sidecars, aborting in-flight http, rejecting its run promises via the new
  transport `close()` methods), then frees the blob bytes its rows referenced
  from the store — the last orphan path the DB cascade left behind.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ction

Snapshots are immutable and the head is just a pointer, so a rollback is a head
move — the storage already allowed it, but no surface exposed it (the doc marked
the endpoint as follow-up work). Add `PUT /projects/:id/snapshots/head` (validates
the snapshot exists, then moves the head under the same project-row lock a deploy
takes), the `katari project rollback SNAPSHOT` command (id-prefix resolution like
the other id args), and a "Make head" action on the console's Snapshots page. A
run pins the snapshot it started on, so only new runs follow the moved head;
nothing in flight is touched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The unit suites mock each layer, so nothing exercised the wire between the
Haskell compiler's IR/schema output and the TypeScript runtime — the gap that
hid the FFI blob 401. This adds `e2e/`: it compiles examples/playground with the
stack-built katari CLI (compile + sidecar bundle + deploy over HTTP) and drives
a real runtime server (compose postgres + s3mock) through runs, files,
escalations, a snapshot rollback, and a mid-run server restart (asserting the
boot-time resume). It provisions and drops its own database and bucket, and is
not part of `pnpm run test` (it needs docker + a Haskell toolchain); run it with
`pnpm run test:e2e`.

Also ignores nested workspace-package node_modules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@yukikurage
yukikurage marked this pull request as ready for review July 4, 2026 17:28
@yukikurage
yukikurage merged commit 0bffd53 into main Jul 4, 2026
2 checks passed
@yukikurage
yukikurage deleted the scrap-and-build-v0.1.0 branch July 9, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant