feat(api): /api/v1 projects, states, labels and work items with the oracle gate on those operations

Đang mở
#58 2 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức phù hợp với người mới
20/100
Loại issue
Tính năng
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
rust, sql
Lĩnh vực
api, backend, databases, devops

Hướng nghiên cứu

Start with crates/thrust-api/src/lib.rs, the migration at crates/thrust-adapters-postgres/migrations/0001_init.sql, and the pinned oracle schema and harness described in the issue. Read the referenced ADRs and resolve the dependencies on #35, #36, and #47 before taking this on. Run scripts/oracle-v1.sh against native-pg and worker-cf; done means both and the Plane control pass with zero unaccepted or stale exceptions, corpus comparisons match, and the 32-operation schema diff has no breaking changes.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

area:app prio:p0 review:fable-2026-10-19

Context

No wire-compatible route exists. thrust_api::router() (crates/thrust-api/src/lib.rs:269-293) puts only the slice's non-Plane routes under /api/v1 (:278-291), and #47 moves them out. Requests authenticate with Authorization: Bearer through the SessionStore (:82-99), not with X-Api-Key. Errors use a Thrust shape, {"error":{"code","message"}} (:47-49), and every authentication failure is a 401 (:54). The schema has projects (id, workspace_id, name) and a work_items table with no state, priority, labels, assignees, parent or dates (crates/thrust-adapters-postgres/migrations/0001_init.sql:4-8,26-41), and no states or labels table. Spike 0008 (bead plane-b6a) built the v1 oracle as the schema, an exception register and a golden corpus (research/0005; ADR-0122 round 26). The oracle exists only on a local, unpushed branch of the Plane fork, feat/plane-b6a-upstream-sync-oracle (research/0005:4-5; Q-0122.4, QUESTIONS.md:293), and it has only ever run against Plane.

Problem

  • M1 includes /api/v1 for projects, states, labels and work items, with the oracle gate on those operations (review §3, FR-01). ADR-0031 keeps v1 wire-compatible and confirms it with the oracle (lines 23, 39). ADR-0032 requires a CI breaking-change diff of the code-first schema (line 41). That diff is not done yet because it "needs the Rust v1 slice" (line 70).
  • In the pinned schema (oracle/schema/openapi.v1.json, 130 operations), these resources are the tags Projects (8), States (5), Labels (5) and Work Items (14): 32 operations, including both the issues/ and work-items/ families, search and lookup by identifier. The fuzzer skips seven of them (the six DELETEs and archive, under SX-001 and SX-002, oracle/exceptions.yaml:25-44). The corpus covers six of those seven; it has no exchange for DELETE .../issues/{pk}/.
  • The gate cannot be pointed at a partial implementation as it stands. run_conformance.py runs the whole schema (:56-80), a register entry that matches nothing fails as stale, and the vacuity floors are sized for the full surface (:205-212; exceptions.yaml:22-23: 100 operations tested, 40 with a 2xx). Only seven of the 12 entries touch the 32 operations (EX-001 to EX-004, EX-006, EX-007, EX-011), so the other five would go stale. Staleness is also counted per entry, so reproducing a quirk on one of an entry's operations hides its absence on the others.
  • The owner chose to reproduce these quirks (ADR-0031 lines 64-74; ADR-0033 lines 79, 83). Only a missing key gets 401; an invalid, expired or inactive key gets 403 {"detail":"Given API token is not valid"} (Plane apps/api/plane/api/middleware/api_authentication.py:32-38). Error bodies come in two shapes. State create returns 200. A per_page of 0 or −1, and some work-item create payloads, give 500 (EX-001, EX-002). ADR-0052 still says "revoke key → 401" (line 53).
  • Every in-scope corpus exchange that passes authentication and permissions carries x-ratelimit-remaining and x-ratelimit-reset; the 401 and 403 answers carry neither (oracle/NORMALISATION.md:23; Plane apps/api/plane/api/rate_limit.py:47-48, default 60/minute at settings/common.py:154). The port can report remaining (crates/thrust-ports/src/lib.rs:208-211). The Workers Rate Limiting binding (ADR-0033, round-29 note) reports only whether a request is allowed.

Proposal

  • Routes: serve the 32 operations under /api/v1/workspaces/{slug}/ as a thin surface over thrust-core use-cases (ADR-0031 line 60). The project and work-item use-cases are shared with #44; the state and label use-cases are new. Each one runs on a &Scope from authorize (#35), with the workspace resolved from {slug} through membership (#36).
  • Schema and types: add workspaces (with slug), workspace_members, api_tokens, states and labels, plus the work-item columns and join tables the v1 serializers return. Plane-shaped DTOs, the pagination envelope and error bodies stay out of thrust-contracts (the rule from #52). A reproduced 500 goes through plane_500() from #51, or is a handled 500 with the pinned body, never a panic (ADR-0031 line 74).
  • Auth: look up X-Api-Key by its SHA-256 hash (ADR-0052 K1; #38 tries this shape). Return 401 for a missing key, and 403 with Plane's body for an invalid, expired or inactive one. Rate-limit per key; a 429 carries {"error_code":5900,"error_message":"RATE_LIMIT_EXCEEDED"} and Retry-After. Send X-RateLimit-Remaining and X-RateLimit-Reset on every response that passes authentication and permissions. On Cloudflare the binding does not report these values, so they are derived; if the countdown cannot match Plane's, record a deviation.
  • Harness home: vendor oracle/ from the fork branch into tools/oracle-v1/ with a SOURCE file that names the commit, or push the branch instead (the owner decides, Q-0122.4). Either way, add the harness to the ADR-0006 tooling register; the uv rule applies.
  • Scoped gate: run_conformance.py --scope m1 passes Schemathesis --include-tag for the four tags (the script already passes --exclude-method and --exclude-path-regex, :77,79). It restricts the register to the in-scope operations, counts staleness per (entry, operation), and uses floors measured on Plane for this scope.
  • Seed and corpus:
    • A fixture loaded through /internal/seed (crates/thrust-api/src/lib.rs:150-156) reproduces the uuid5 ids, names and seven tokens of oracle/scripts/seed.py, so seed-manifest.json applies unchanged. Before every gate and control run, a reset script loads the fixture again.
    • record_corpus.py --only (:427) records against Thrust, and the result is compared exchange by exchange with the committed corpus for the in-scope operations. Add the missing DELETE .../issues/{pk}/ exchange to the corpus, recorded on Plane first.
    • scripts/oracle-v1.sh <target> runs the gate and the corpus comparison, and is one of the planned cells of gate.
  • Schema diff: generate the 32 operations with utoipa, then run oasdiff breaking against the pinned schema restricted to those operations. The EX-006 nullables are recorded exceptions. Never pass --open.

Acceptance criteria

  • scripts/oracle-v1.sh native-pg and scripts/oracle-v1.sh worker-cf end in VERDICT: PASS with unaccepted=0, stale=0 (per entry and operation) and the scoped floors met. The same scoped run against Plane passes too.
  • Control: with run_controls.py --upstream <thrust>, the gate fails C1, C2 and C3 (each breaks one field of GET .../states/, run_controls.py:33-44) on that operation, and passes C0. The corpus comparison catches C4 (a dropped id, which the schema cannot see). The results are committed.
  • After normalisation, the corpus comparison shows no difference for the in-scope exchanges in projects, states, labels, work_items, pagination and errors. A run that compares zero exchanges fails. Control: making state create return 201 makes it fail.
  • Contract tests pass on all four targets: no key gives 401; a bogus, expired or inactive key gives 403 with Plane's body; a create by a guest gives 403.
  • oasdiff reports no breaking change for the 32 operations, and the generated schema is committed.
  • The four list endpoints stay within the round-trip budget from #39.

References

  • Review: .agents/plans/contrail/reviews/2026-10-19-fable-architecture-review.md (FR-01; §3 M1 row; FR-06)
  • ADR-0031 (lines 23, 39, 60, 64-74), ADR-0032 (lines 29, 41, 70), ADR-0122 (lines 44, 77-79, 86, 90), ADR-0033 (lines 31, 79, 83; round-29 note), ADR-0052 (line 53), ADR-0006 (register), ADR-0034 (round-29 note)
  • research/0005-oracle-harness.md (lines 4-5, 49-55, 64-72, 88-89); spikes/0008-v1-oracle-harness.md; FEATURES §A5 (FEATURES.md:88-91), F-X06 (:135); Q-0122, Q-0122.4, Q-0120.3, Q-0033.2; DEVIATIONS C-2 (DEVIATIONS.md:32), D-002, D-007, D-013
  • crates/thrust-api/src/lib.rs:47-49,54,82-99,150-156,269-293; crates/thrust-ports/src/lib.rs:208-211; crates/thrust-adapters-postgres/migrations/0001_init.sql:4-8,26-41
  • Plane fork branch feat/plane-b6a-upstream-sync-oracle (72b2e01497): oracle/exceptions.yaml:12-23,25-44,45-89; oracle/harness/run_conformance.py:56-80,205-212; oracle/harness/run_controls.py:33-44; oracle/harness/record_corpus.py:427; oracle/NORMALISATION.md:23; oracle/scripts/seed.py; oracle/corpus/seed-manifest.json
  • Plane oracle (oracle/v1-pin-20261018): apps/api/plane/api/middleware/api_authentication.py:32-38; apps/api/plane/api/rate_limit.py:12-48; apps/api/plane/settings/common.py:154

Blocked by: #35, #36, #47

Finding: FR-01

Ngôn ngữ chính
Rust
Star
0
Fork
0
Merge trung bình
1 giờ 35 phút
Pull request đã merge (30 ngày)
67

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của aRustyDev/thrust

Tất cả issue của aRustyDev/thrust

Issue tương tự

Thêm issue về Rust

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.