feat(p1-6): kb-store-sqlite — P1 terminal task #11

Merged
altair823 merged 9 commits from feat/p1-6-store-sqlite into main 2026-04-30 17:47:47 +00:00
Owner

요약

P1의 마지막 task인 kb-store-sqlite를 구현했습니다. P1 ingest 파이프라인(walk → parse → chunk → store)을 종결하며, design §5 DDL 전체(13 tables + 15 indexes)와 DocumentStore / JobRepo 두 trait의 풀 구현, 자산 라이터, 그리고 P1-5에서 미뤄진 kb_core::Chunk.policy_hash schema reconcile을 함께 포함합니다.

기준 문서: tasks/p1/p1-6-store-sqlite.md, docs/superpowers/specs/2026-04-27-kb-final-form-design.md (§5 ALL DDL / §5.8 transactions / §6.3 data_dir / §7.2 traits / §10 errors).

구현

kb-core schema reconcile (P1-5 review에서 deferred된 항목)

  • kb_core::Chunkpolicy_hash: String 필드 추가. chunk_id recipe(§4.2)는 이미 동일 값을 사용하고 있어 ID는 변하지 않고 wire form만 보강됨.
  • kb-chunk::build_chunk가 새 필드를 채우도록 갱신.
  • fixtures/markdown/long-section.chunks.snapshot.json baseline 재생성 — policy_hash 8줄만 추가, chunk_id는 변동 없음.

crates/kb-store-sqlite

  • Cargo.toml — production deps만 사용 (kb-core, kb-config, anyhow, blake3, refinery, rusqlite[bundled], serde_json, thiserror, time, tracing). kb-parse-md/kb-normalize/kb-chunk는 dev-only.
  • src/store.rsSqliteStore::open + pragmas (foreign_keys=ON, journal_mode=WAL, synchronous=NORMAL, temp_store=MEMORY) + run_migrations + 자산 atomic writer + lock_conn poison-recovery helper + validate_asset_id (32 ASCII hex 강제).
  • src/documents.rsDocumentStore 7개 메서드 (idempotent UPSERT, doc_version 자동 증분, blocks/chunks DELETE-then-INSERT, document_tags 재도출).
  • src/jobs.rsJobRepo 4개 메서드 (pending → running 자동 전이, 에러 round-trip).
  • src/error.rsStoreError { Sqlite, Migration, Conflict }.
  • migrations/V001__init.sql — §5.1-§5.7 DDL 전체. FTS5 가상 테이블/트리거는 P2-1 V002로 명시 연기.
  • tests/ — 15개 통합 테스트 (8 카테고리 + 추가 회귀).

동작 contract 핵심

  • 자산 atomic write: <final>.tmp.<pid>.<atomic_counter>에 쓴 뒤 fsync → row UPSERT → atomic rename. UPSERT 실패 시 temp 파일 best-effort cleanup으로 고아 파일 차단.
  • AssetId 입력 검증: put_asset_with_bytes/put_asset 진입점에서 32 ASCII hex 검사. path-traversal 방어.
  • Mutex poison 회복: 모든 connection 획득이 lock_conn().unwrap_or_else(|p| p.into_inner())을 거쳐 한 번의 panic이 store 전체를 무력화하지 않도록 함.
  • Idempotency: re-ingest 시 documents UPSERT + doc_version + 1, blocks/chunks/document_tags DELETE-then-INSERT.
  • 트랜잭션: 메서드별 트랜잭션 (put_document / put_blocks / put_chunks 각각 BEGIN..COMMIT). 다중 메서드 묶음은 kb-app의 컴포지션 책임으로 명시.

검증

  • cargo check / build (RUSTFLAGS=\"-D warnings\") / clippy --all-targets -D warnings clean
  • cargo test -p kb-store-sqlite15 통과 (3 asset writer + 1 migration + 3 idempotency + 1 round-trip + 1 ingest_report snapshot + 3 jobs + 1 list_docs + 2 신규 회귀 테스트)
  • cargo test --workspace 전체 그린
  • cargo tree -p kb-store-sqlite --depth 1 → 허용 deps만 (parser/normalize/chunk crate 누출 없음)

Review trail

  • spec compliance: PASS-WITH-NICE-TO-FIX — must-fix 0, 4 nice-to-fix (모두 P2-1로 자연 해소)
  • code quality round 1: APPROVED-WITH-MINOR — Important 3건 (I1 자산 atomic write / I2 mutex poison / I3 AssetId 검증) + Minor 4건
  • 반영 3 commit (e41279d, 15b4d80, b7367de) — 신규 회귀 테스트 2건 포함, 기존 13건 유지
  • code quality round 2 (re-review): APPROVED — must-fix-now 0건

후속 자연 해소 (비차단, P2-1로 이연)

  • I4: schema_meta 테이블 활성화 (§5.9 권위와 refinery bookkeeping의 정합 결정 — V002에서 정리)
  • I5: IngestReport JSON Schema 검증 단계 추가 (docs/wire-schema/v1/ingest_report.schema.json 검사)
  • §3.5 design doc Chunk 정의에 policy_hash 필드 갱신 (doc-only)
  • list_documents N+1 tags re-query 최적화 (P2 데이터양 측정 후)
  • kb_core::AssetId::try_new(s) -> Result<Self> 추가하여 검증 단일 출처화

다음

P1 마지막 PR입니다. 머지 후 P2-1(kb-search lexical / FTS5) 단계로 진입합니다. P2-1 시작 시 위 후속 항목들을 함께 정리할 예정입니다.

## 요약 P1의 마지막 task인 `kb-store-sqlite`를 구현했습니다. P1 ingest 파이프라인(walk → parse → chunk → store)을 종결하며, design §5 DDL 전체(13 tables + 15 indexes)와 `DocumentStore` / `JobRepo` 두 trait의 풀 구현, 자산 라이터, 그리고 P1-5에서 미뤄진 `kb_core::Chunk.policy_hash` schema reconcile을 함께 포함합니다. 기준 문서: tasks/p1/p1-6-store-sqlite.md, docs/superpowers/specs/2026-04-27-kb-final-form-design.md (§5 ALL DDL / §5.8 transactions / §6.3 data_dir / §7.2 traits / §10 errors). ## 구현 ### kb-core schema reconcile (P1-5 review에서 deferred된 항목) - `kb_core::Chunk`에 `policy_hash: String` 필드 추가. `chunk_id` recipe(§4.2)는 이미 동일 값을 사용하고 있어 ID는 변하지 않고 wire form만 보강됨. - `kb-chunk::build_chunk`가 새 필드를 채우도록 갱신. - `fixtures/markdown/long-section.chunks.snapshot.json` baseline 재생성 — `policy_hash` 8줄만 추가, `chunk_id`는 변동 없음. ### `crates/kb-store-sqlite` - `Cargo.toml` — production deps만 사용 (kb-core, kb-config, anyhow, blake3, refinery, rusqlite[bundled], serde_json, thiserror, time, tracing). `kb-parse-md`/`kb-normalize`/`kb-chunk`는 dev-only. - `src/store.rs` — `SqliteStore::open` + pragmas (foreign_keys=ON, journal_mode=WAL, synchronous=NORMAL, temp_store=MEMORY) + `run_migrations` + 자산 atomic writer + `lock_conn` poison-recovery helper + `validate_asset_id` (32 ASCII hex 강제). - `src/documents.rs` — `DocumentStore` 7개 메서드 (idempotent UPSERT, doc_version 자동 증분, blocks/chunks DELETE-then-INSERT, document_tags 재도출). - `src/jobs.rs` — `JobRepo` 4개 메서드 (pending → running 자동 전이, 에러 round-trip). - `src/error.rs` — `StoreError { Sqlite, Migration, Conflict }`. - `migrations/V001__init.sql` — §5.1-§5.7 DDL 전체. FTS5 가상 테이블/트리거는 P2-1 V002로 명시 연기. - `tests/` — 15개 통합 테스트 (8 카테고리 + 추가 회귀). ### 동작 contract 핵심 - 자산 atomic write: `<final>.tmp.<pid>.<atomic_counter>`에 쓴 뒤 fsync → row UPSERT → atomic `rename`. UPSERT 실패 시 temp 파일 best-effort cleanup으로 고아 파일 차단. - AssetId 입력 검증: `put_asset_with_bytes`/`put_asset` 진입점에서 32 ASCII hex 검사. path-traversal 방어. - Mutex poison 회복: 모든 connection 획득이 `lock_conn().unwrap_or_else(|p| p.into_inner())`을 거쳐 한 번의 panic이 store 전체를 무력화하지 않도록 함. - Idempotency: re-ingest 시 documents UPSERT + `doc_version + 1`, blocks/chunks/document_tags DELETE-then-INSERT. - 트랜잭션: 메서드별 트랜잭션 (put_document / put_blocks / put_chunks 각각 BEGIN..COMMIT). 다중 메서드 묶음은 `kb-app`의 컴포지션 책임으로 명시. ## 검증 - `cargo check / build (RUSTFLAGS=\"-D warnings\") / clippy --all-targets -D warnings` clean - `cargo test -p kb-store-sqlite` → **15 통과** (3 asset writer + 1 migration + 3 idempotency + 1 round-trip + 1 ingest_report snapshot + 3 jobs + 1 list_docs + 2 신규 회귀 테스트) - `cargo test --workspace` 전체 그린 - `cargo tree -p kb-store-sqlite --depth 1` → 허용 deps만 (parser/normalize/chunk crate 누출 없음) ## Review trail - spec compliance: PASS-WITH-NICE-TO-FIX — must-fix 0, 4 nice-to-fix (모두 P2-1로 자연 해소) - code quality round 1: APPROVED-WITH-MINOR — Important 3건 (I1 자산 atomic write / I2 mutex poison / I3 AssetId 검증) + Minor 4건 - 반영 3 commit (`e41279d`, `15b4d80`, `b7367de`) — 신규 회귀 테스트 2건 포함, 기존 13건 유지 - code quality round 2 (re-review): **APPROVED** — must-fix-now 0건 ## 후속 자연 해소 (비차단, P2-1로 이연) - I4: `schema_meta` 테이블 활성화 (§5.9 권위와 refinery bookkeeping의 정합 결정 — V002에서 정리) - I5: `IngestReport` JSON Schema 검증 단계 추가 (`docs/wire-schema/v1/ingest_report.schema.json` 검사) - §3.5 design doc Chunk 정의에 `policy_hash` 필드 갱신 (doc-only) - `list_documents` N+1 tags re-query 최적화 (P2 데이터양 측정 후) - `kb_core::AssetId::try_new(s) -> Result<Self>` 추가하여 검증 단일 출처화 ## 다음 P1 마지막 PR입니다. 머지 후 P2-1(`kb-search` lexical / FTS5) 단계로 진입합니다. P2-1 시작 시 위 후속 항목들을 함께 정리할 예정입니다.
altair823 added 9 commits 2026-04-30 17:38:59 +00:00
Add policy_hash: String to kb_core::Chunk to align with the §5.5 SQLite
schema (chunks.policy_hash NOT NULL), so kb-store-sqlite persistence is a
straight field copy rather than a recompute.

This is a §9 schema migration:
- §5.5 (the persistence schema) is authoritative.
- §3.5 (the domain model) must accommodate.

The chunker already computed policy_hash for the chunk_id recipe (§4.2);
P1-5 stored it implicitly. We now hold it explicitly on the Chunk so any
DocumentStore::put_chunks impl can read it directly.

Follow-up commits update kb-chunk to populate the field and refresh the
P1-5 snapshot baseline accordingly.
Set the new policy_hash field on every emitted Chunk to the same hex
already computed for the chunk_id recipe (§4.2). No recipe / chunk_id
change — only the field on the struct is now populated.

Pairs with the kb-core hotfix (preceding commit) and unblocks P1-6's
DocumentStore::put_chunks to read chunk.policy_hash directly per §5.5.
The snapshot now includes the policy_hash field on every Chunk per the
preceding kb-core schema change. chunk_ids are unchanged because the
chunk_id recipe (§4.2) already incorporated policy_hash via the chunker —
the field is simply now visible in the wire form.

Regenerated via:
  UPDATE_SNAPSHOTS=1 cargo test -p kb-chunk long_section_chunks_snapshot
New workspace member crate `kb-store-sqlite` (allowed deps only:
kb-core, kb-config, rusqlite[bundled], refinery, serde, serde_json,
time, blake3, tracing, anyhow, thiserror; dev-deps add kb-parse-md /
kb-normalize / kb-chunk for the contract round-trip test).

Migration V001 replaces the P0-1 stub with the full §5 DDL (assets,
documents, document_tags, blocks, chunks with policy_hash,
embedding_records, jobs, ingest_runs, answers, eval_runs,
eval_query_results) plus the §5 indexes. FTS5 virtual table + triggers
remain deferred to V002 (P2-1).

Public surface per task spec:
  SqliteStore::open / run_migrations / put_asset_with_bytes
  impl DocumentStore for SqliteStore (7 trait methods)
  impl JobRepo for SqliteStore (4 trait methods)
  StoreError { Sqlx, Migration, Conflict }

Behavior:
- Pragmas at open: foreign_keys=ON, journal_mode=WAL,
  synchronous=NORMAL, temp_store=MEMORY.
- Asset writer: byte_len ≤ copy_threshold_mb * 1MiB → copy to
  data_dir/assets/<aa>/<asset_id> (mode 0o644 on Unix), else
  reference. blake3(bytes) verified against asset.checksum; mismatch →
  Conflict.
- Idempotency: put_document UPSERTs and bumps doc_version + 1 on
  conflict; put_blocks / put_chunks DELETE-then-INSERT; document_tags
  re-derived per put_document.
- get_document rehydrates blocks via payload_json ordered by stream
  ordinal.
- list_documents builds dynamic WHERE from DocFilter (lang / trust_min
  / path_glob via GLOB / tags_any via document_tags subquery).
- JobRepo: jobs.kind/status are stored as lowercase enum tags; create
  mints a 32-hex JobId via blake3(kind || payload || nanos).

Tests follow in subsequent commits.
All 8 test categories from the task plan, plus a JobRepo subset:

  migration   — tests/migration.rs: fresh DB after run_migrations
                exposes every required §5 table + index.
  unit (copy) — tests/asset_writer.rs: copy mode writes file with
                mode 0o644 + correct bytes.
  unit (ref)  — tests/asset_writer.rs: reference mode does not write
                file; row records source path.
  unit (cs)   — tests/asset_writer.rs: tampered checksum returns a
                Conflict-flavoured anyhow error.
  unit (idem) — tests/idempotency.rs: same put_document twice → 1 row,
                doc_version 1→2; tags re-derived.
  unit (rb)   — tests/idempotency.rs: put_blocks with FK violation
                rolls back; pre-existing rows unchanged.
  contract    — tests/contract_roundtrip.rs: drives kb-parse-md +
                kb-normalize + kb-chunk on
                fixtures/markdown/code-and-table.md, persists, then
                reloads via DocumentStore::get_document /
                get_chunk and asserts byte-equal round-trip.
  snapshot    — tests/ingest_report_snapshot.rs +
                snapshots/ingest_report.snapshot.json: pin the wire
                JSON form of kb_core::IngestReport for an inline
                fixture run.
  jobs        — tests/jobs.rs: create → progress → finish flow;
                error message round-trip; list filters on status/kind.

Drops the unused `serde` direct dep from Cargo.toml; serde_json brings
its own. Dev-deps confirmed via `cargo tree -p kb-store-sqlite --depth 1`
to live only in the dev tree.
Round-trip three docs (en/ko, varied tags, varied trust) and exercise
each DocFilter axis: default (all), lang, path_glob (workspace_path
GLOB), tags_any (intersection via document_tags subquery + per-row tag
hydration), and trust_min (Primary > Secondary > Generated rank gate).
Three Important review fixes on the kb-store-sqlite write path:

I1. Atomic asset write. put_asset_with_bytes now stages bytes to
    `<final>.tmp.<pid>.<n>`, fsyncs, UPSERTs the row, then `rename`s into
    place (atomic on POSIX same-fs). On any failure between staging and
    rename we best-effort `remove_file` the temp so the previous orphan
    risk on UPSERT failure is gone. Reference mode is unchanged (no I/O,
    no orphan risk).

I2. Poison-tolerant mutex. New `lock_conn` helper does
    `.lock().unwrap_or_else(|p| p.into_inner())`, so a single panic mid-
    transaction no longer poisons every subsequent store call. The
    rusqlite Transaction Drop already rolls back on panic, leaving the
    Connection state safe to reuse. All 13 prior `.expect("sqlite mutex
    poisoned")` sites in store.rs / documents.rs / jobs.rs now route
    through `lock_conn`.

I3. AssetId shape validation. `kb_core::AssetId(pub String)` lets a
    hand-construction bypass the `FromStr` 32-hex invariant. Added
    `validate_asset_id` (32 ASCII hex chars) at every store entry that
    turns an AssetId into a path: `put_asset_with_bytes` and
    `DocumentStore::put_asset`. This shuts a potential path-traversal via
    `assets_path_for`'s `&id[..2]` shard slice.

Tests:
- `put_asset_with_bytes_orphan_cleanup_on_upsert_failure` — pre-seeds a
  row that takes the same `workspace_path` (UNIQUE), so the UPSERT trips
  a constraint not covered by `ON CONFLICT(asset_id)`. Asserts no final
  file and no leaked `*.tmp.*`.
- `put_asset_with_bytes_rejects_invalid_asset_id` — passes
  `AssetId("../etc/passwd_padded_to_xx_xxxxx")` (32 chars, contains `/`).
  Asserts error and zero filesystem artifacts under `data_dir/assets/`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
M1: `Sqlx` is a misleading leftover — this crate uses `rusqlite`, not
sqlx. Rename the variant (and the doc reference to it) to `Sqlite`. No
external pattern matches; the variant is reached only via `#[from]`.

M11: `assets_root` was an `#[allow(dead_code)]` helper introduced for a
test that never landed. Delete it so the dead-code allow goes with it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
M9: add a `TODO(P2/P3)` comment near the NULL persistence at
documents.rs (put_chunks). The `section_label` column exists in the §5.5
DDL but neither the in-memory Chunk struct nor the §2.6 wire schema
carries the field, so NULL is the correct canonical value today —
flag the future-bump intent in-line rather than leaving it implicit.

M10: add a one-line invariant comment near the i64 -> u32 narrowing for
`doc_version` in `get_document`. The invariant is documented at the
write site (UPSERT bumps by 1 per re-ingest) — restate it at the read
site so the cast is not silently load-bearing.

No behaviour change. No tests touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claude-reviewer-01 reviewed 2026-04-30 17:39:22 +00:00
claude-reviewer-01 left a comment
Member

리뷰: 모든 항목 해소 — APPROVE 권고 (gate 정책상 COMMENT 등록)

내부 review loop를 3회 완료했습니다. 마지막 리뷰어 verdict: APPROVED, must-fix-now 0건.

Review trail

Round Verdict 반영
Spec compliance PASS-WITH-NICE-TO-FIX (must-fix 0, 4 nice-to-fix → P2-1 이연)
Quality round 1 APPROVED-WITH-MINOR (Important 3 + Minor 4) e41279d, 15b4d80, b7367de
Quality round 2 APPROVED (must-fix-now 0)

핵심 검증

항목 상태
§5 DDL 전체 (13 tables + 15 indexes) verbatim 일치 FTS5는 P2-1 V002로 명시 연기
DocumentStore 7 trait method + JobRepo 4 trait method §7.2 시그니처와 일치
Pragmas (foreign_keys=ON / WAL / synchronous=NORMAL / temp_store=MEMORY) open마다 적용
자산 atomic write (temp file + fsync + rename + cleanup) 모든 에러 경로에서 고아 파일 차단 (I1)
Mutex poison recovery via lock_conn helper 13개 호출지 일괄 적용, panic으로 store 무력화 차단 (I2)
AssetId 32-hex 검증 (path traversal 방어) put_asset_with_bytes/put_asset 진입점 (I3)
Idempotency (UPSERT + doc_version + 1, blocks/chunks DELETE-INSERT)
Transaction rollback on FK 위반 put_blocks_transactional_rollback_on_fk_violation
kb_core::Chunk.policy_hash schema reconcile chunk_id 불변, P1-5 snapshot 정합

Gate 통과

  • cargo check / build (RUSTFLAGS=-D warnings) / clippy --all-targets -D warnings clean
  • cargo test -p kb-store-sqlite15 통과 (was 13, +2 신규: orphan cleanup / invalid AssetId rejection)
  • cargo test --workspace 전체 그린
  • cargo tree -p kb-store-sqlite --depth 1 → 허용 deps만 (kb-parse-md/kb-normalize/kb-chunk는 dev-only)

후속 자연 해소 (비차단, P2-1로 이연)

  • schema_meta 테이블 활성화 + refinery bookkeeping과의 정합 (§5.9)
  • IngestReport JSON Schema 검증 단계
  • kb_core::AssetId::try_new 도입하여 store 측 validate_asset_id 중복 제거
  • §3.5 design doc Chunk 정의에 policy_hash 추가 (doc-only)
  • list_documents N+1 prepare 최적화 (perf, 데이터양 측정 후)

결론

P1의 마지막 빌딩블록이 안정적으로 마무리되었습니다. self-review gate 정책상 본 코멘트는 COMMENT — author 측 수동 APPROVE + merge 부탁드립니다.

## 리뷰: 모든 항목 해소 — APPROVE 권고 (gate 정책상 COMMENT 등록) 내부 review loop를 3회 완료했습니다. 마지막 리뷰어 verdict: **APPROVED**, must-fix-now 0건. ### Review trail | Round | Verdict | 반영 | |-------|---------|------| | Spec compliance | PASS-WITH-NICE-TO-FIX (must-fix 0, 4 nice-to-fix → P2-1 이연) | — | | Quality round 1 | APPROVED-WITH-MINOR (Important 3 + Minor 4) | `e41279d`, `15b4d80`, `b7367de` | | Quality round 2 | **APPROVED** (must-fix-now 0) | — | ### 핵심 검증 | 항목 | 상태 | |------|------| | §5 DDL 전체 (13 tables + 15 indexes) verbatim 일치 | ✅ FTS5는 P2-1 V002로 명시 연기 | | `DocumentStore` 7 trait method + `JobRepo` 4 trait method | ✅ §7.2 시그니처와 일치 | | Pragmas (foreign_keys=ON / WAL / synchronous=NORMAL / temp_store=MEMORY) | ✅ open마다 적용 | | 자산 atomic write (temp file + fsync + rename + cleanup) | ✅ 모든 에러 경로에서 고아 파일 차단 (I1) | | Mutex poison recovery via `lock_conn` helper | ✅ 13개 호출지 일괄 적용, panic으로 store 무력화 차단 (I2) | | AssetId 32-hex 검증 (path traversal 방어) | ✅ `put_asset_with_bytes`/`put_asset` 진입점 (I3) | | Idempotency (UPSERT + `doc_version + 1`, blocks/chunks DELETE-INSERT) | ✅ | | Transaction rollback on FK 위반 | ✅ `put_blocks_transactional_rollback_on_fk_violation` 핀 | | `kb_core::Chunk.policy_hash` schema reconcile | ✅ chunk_id 불변, P1-5 snapshot 정합 | ### Gate 통과 - `cargo check / build (RUSTFLAGS=-D warnings) / clippy --all-targets -D warnings` clean - `cargo test -p kb-store-sqlite` → **15 통과** (was 13, +2 신규: orphan cleanup / invalid AssetId rejection) - `cargo test --workspace` 전체 그린 - `cargo tree -p kb-store-sqlite --depth 1` → 허용 deps만 (`kb-parse-md`/`kb-normalize`/`kb-chunk`는 dev-only) ### 후속 자연 해소 (비차단, P2-1로 이연) - `schema_meta` 테이블 활성화 + refinery bookkeeping과의 정합 (§5.9) - `IngestReport` JSON Schema 검증 단계 - `kb_core::AssetId::try_new` 도입하여 store 측 `validate_asset_id` 중복 제거 - §3.5 design doc Chunk 정의에 `policy_hash` 추가 (doc-only) - `list_documents` N+1 prepare 최적화 (perf, 데이터양 측정 후) ### 결론 P1의 마지막 빌딩블록이 안정적으로 마무리되었습니다. self-review gate 정책상 본 코멘트는 COMMENT — author 측 수동 APPROVE + merge 부탁드립니다.
altair823 merged commit fd11dd054b into main 2026-04-30 17:47:47 +00:00
altair823 deleted branch feat/p1-6-store-sqlite 2026-04-30 17:47:49 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: altair823-org/kebab#11