📋

PROJ

P1 · Internal productivity Planned · P1 design phase Owner: COO seat (interim CEO)

Project management for consultancies — Linear's three-primitive model (Issue · Cycle · Project), plus an Engagement primitive for the contract under which delivery happens, with a sync-engine that feels instant.

PROJ adopts the Linear / Height / Cycle / Plane model that has been validated by every modern consultancy-friendly issue tracker: Issues are the unit of work, Cycles are the 1-2 week iterations issues are scheduled into, and Projects are durable containers that aggregate issues across cycles. CyberOS adds the Engagement primitive — the contract with a Client under which a Project is delivered — because consultancies need the rate-card, billable / non-billable split, and TIME→INV flow that pure product-shop tools assume away. Mutation UX is sync-engine: every change applies optimistically to local state, propagates via WebSocket, and the server-canonical state wins on conflict. Yjs CRDTs handle the offline-edit case. Vietnamese localisation covers status names, salutations, and UI strings. PRD §9.5 locks the FRs; this page documents the planned implementation at cyberos/services/proj/.

PROJ is CyberOS's project tracker, sprint engine, and Engagement billing surface in one. The data model is Issue → Cycle → Project plus Engagement (the contract). Status is a closed enum (backlog · todo · in-progress · in-review · done · cancelled) with a configurable per-project workflow on top. Priority is the standard urgent · high · medium · low · none. Mutations are optimistic locally, server-canonical eventually; Yjs CRDTs let two Members edit the same description offline and merge without conflict. Every issue can link to BRAIN entries — a memory becomes a citation, a decision-log row becomes a sub-task. AI features are first-class: CUO drafts the cycle review at end-of-cycle; the blocker-detector watches comments for "blocked by"; estimate calibration tracks estimated vs actual hours per Member per task class. PRD §9.5.

Status
Planned
P1 · design phase
Primitives
4
Issue · Cycle · Project · Engagement
Sync
Optimistic
WebSocket + server-canonical
Offline
Yjs CRDT
offline-edit + merge
i18n
vi + en
status / UI strings
AI features
4
triage · blocker · review · calibration
Depends on
AUTH · BRAIN · TIME
+ AI · MCP · OBS
Est. LoC
~11,000
Rust + TS SPA + Yjs
1

Why PROJ exists

Linear's data-model insight was that three primitives — Issue, Cycle, Project — cover almost every workflow that product and services teams actually do, and that a fourth primitive ("Engagement") covers the consultancy case once you add a rate card and a billable / non-billable distinction. The UX insight was sync-engine: the user never waits for a server round-trip; the server is the eventual authority, but the local copy is the one being typed into. PROJ replicates both insights and adds the CyberOS-specific bindings: every issue links to BRAIN memories (so a decision-log row can spawn a sub-task), every comment is a candidate for a blocker-detection nudge, every cycle ends with a CUO-drafted review the Account Manager can edit before sending to the Client.

Sync-engine feel

Optimistic local updates, WebSocket fan-out, server-canonical rebase on conflict. No spinners on the hot path; offline edits merge via Yjs CRDT.

📑
Engagement primitive

Where Linear has "Project", we have "Project under Engagement". Rate cards, billable rules, and TIME → INV flow ride on the Engagement.

🧠
BRAIN-linked work

Every issue can cite a BRAIN memory; tasks are ingested into Layer 3; cross-project pattern queries answer "what's the pattern in late tasks?".

The bet is that consultancies do not need a different tracker per client — they need one tracker that understands the consulting shape natively. Linear validated the three-primitive model; CyberOS extends it with the contract layer and the AI-native loop. The sync-engine is the rest of the way to "feels instant" — Linear documented the pattern; we re-implement it because the consulting cycle is two-weeks-anchored to a Client review, and that review needs to be CUO-draftable from the comment stream.

2

What it does — 5W1H2C5M

A structured decomposition of PROJ's scope. Every cell traces back to PRD §9.5.

AxisQuestionAnswer
5W · WhatWhat is PROJ?An issue tracker with four primitives (Issue, Cycle, Project, Engagement). Sync-engine for the SPA; Yjs CRDT for offline-edit merges; PGroonga for Vietnamese full-text; BRAIN integration both ways (issues link to memories, completed issues ingested as Layer 3 nodes).
5W · WhoWho uses it?Members: create / assign / progress issues; review their own cycle queue daily. Account Managers: own the Engagement; edit + send CUO-drafted cycle reviews. Clients: read-only Project view via PORTAL (P2). Agents: CUO Notify on blocker detection; CUO/COO-skill triage suggestions.
5W · WhenWhen does it run?Continuous: WebSocket fan-out on every mutation; nightly batch for estimate-calibration metric refresh; end-of-cycle batch for review draft generation. Auto-roll of incomplete tasks at cycle close (configurable per team).
5W · WhereWhere does it run?P1: single region (SG-1) with VN-residency S3 for tenant uploads. P3+: multi-region active-active. The sync server is a per-tenant axum binary; the SPA is hosted as static assets.
5W · WhyWhy a separate tracker?Existing trackers do not natively model the Engagement / rate-card / billable-time loop; sync-engine UX is rare outside Linear; and BRAIN-linked work is a CyberOS-specific need that no off-the-shelf tracker provides.
1H · HowHow does it work?Mutations: client-side optimistic apply → JMAP-like RPC over WebSocket → server validates → server-canonical state broadcast to all connected SPAs → conflicts resolved server-side and rebased into the client. CRDT: long-form fields (description, comment body) are Yjs documents; the rest is last-write-wins with a vector clock.
2C · CostCost budget?P1: ~$80 / month for SG-1 single-tenant pilot. 50-tenant: ~$340 / month (RDS + Redis + Fargate + S3). Per-issue write cost ~$0.0000004.
2C · ConstraintsConstraints?(a) Issues ingested into BRAIN Layer 3 — but body content gates as per (FR pending). (b) Per-task ACL — private engagements MUST NOT be visible to non-engaged Members. (c) Vietnamese localisation of status / UI strings — non-negotiable for P1 launch.
5M · MaterialsStack?Rust 1.81 · axum 0.7 · sqlx · PostgreSQL 16 + PGroonga · Redis 7 · S3 + KMS · Yjs (server-side ywasm + client TS) · TypeScript SPA (React + Zustand) · WebSocket fan-out via NATS JetStream · OpenTelemetry SDK.
5M · MethodsMethod choices?Three-primitive model (Linear). Sync-engine UX (Linear). Yjs for CRDT long-form (because OT is too brittle at scale). Closed status enum + workflow overlay (not free-form labels). PGroonga for VN search. NATS JetStream for fan-out (not a Postgres LISTEN/NOTIFY — won't scale past 1k WS connections).
5M · MachinesDeployment?Per-tenant Fargate axum binary handling WebSocket + REST. Postgres RDS Multi-AZ. Redis for hot-cache and WS rooms. NATS JetStream cluster (3 nodes P2+). S3 for attachments.
5M · ManpowerWho maintains?0.75 FTE (CTO seat) at P1 launch. By P2+: COO seat owns product + Account Manager workflow; CTO owns engine + sync layer.
5M · MeasurementHow measured?Issue write p95 ≤ 120 ms; SPA list-view load p95 ≤ 250 ms; offline-merge correctness ≥ 99.9% (property tests); BRAIN ingestion p95 ≤ 5 s; cycle-review draft acceptance rate ≥ 60% by Account Manager.
3

Architecture

PROJ is one axum binary with four surfaces (sync-engine WebSocket, REST admin, GraphQL federated subgraph, MCP tool catalogue) and three stores (Postgres for canonical state + Yjs document snapshots, Redis for WS rooms and search cache, S3 for attachments). NATS JetStream fans mutations out to every connected SPA.

graph TB subgraph CLIENT ["Clients"] SPA["SPA
React + Zustand"] MOBILE["Mobile (P3)"] AGENT["🎯 CUO agent
via MCP"] end subgraph EDGE ["Edge"] WSGW["WebSocket gateway
JWT-authed"] GQL["GraphQL subgraph
(federated)"] REST["Admin REST"] MCP["MCP tool surface"] end subgraph CORE ["PROJ service (Rust)"] SYNC["Sync engine
optimistic + rebase"] CRDT["Yjs document server
(ywasm)"] WF["Workflow engine
status transitions"] AI["AI hooks
blocker · review · calibration"] BRAIN_ING["BRAIN ingestor
Layer 3"] AUTH_CHK["RBAC + per-task ACL"] end subgraph STORES ["Stores"] PG[("PostgreSQL + PGroonga
issues · cycles · projects
RLS by tenant_id")] RED[("Redis 7
WS rooms · search cache")] S3[("S3 + KMS
attachments")] NATS[("NATS JetStream
mutation fan-out")] end subgraph SINKS ["Sinks"] BRAIN["🧠 BRAIN
Layer 3 ingest · audit"] TIME["⏱ TIME
billable / non-billable"] INV["🧾 INV
billable hours → invoice"] OBS["👁 OBS
traces + SLO"] CUO["🎯 CUO
Notify · review draft"] end SPA --> WSGW MOBILE --> WSGW AGENT --> MCP WSGW --> SYNC GQL --> SYNC REST --> SYNC MCP --> SYNC SYNC --> CRDT SYNC --> WF SYNC --> AI SYNC --> BRAIN_ING SYNC --> AUTH_CHK SYNC --> PG SYNC --> RED SYNC --> NATS NATS --> WSGW CRDT --> PG AI --> CUO BRAIN_ING --> BRAIN WF --> TIME TIME --> INV SYNC --> S3 SYNC --> OBS classDef planned fill:#cba88a,stroke:#45210e classDef store fill:#f5f3ff,stroke:#7c3aed classDef sink fill:#f5ede6,stroke:#45210e class SPA,MOBILE,AGENT,WSGW,GQL,REST,MCP,SYNC,CRDT,WF,AI,BRAIN_ING,AUTH_CHK planned class PG,RED,S3,NATS store class BRAIN,TIME,INV,OBS,CUO sink

The four primitives

Issue (the atomic unit)

A discrete piece of work. Has title, description (Yjs), status, priority, assignee, estimate, labels, dependencies, parent issue, cycle binding. Comments are sub-resources.

Cycle (1-2 week sprint)

A time-boxed bucket of Issues. Has start / end dates, target velocity, retro link. Incomplete Issues auto-roll to the next cycle if the team configures it ((FR pending)).

Project (durable container)

A multi-cycle initiative. Has roadmap view, status (planned / active / paused / completed), client visibility flag (visible via PORTAL when the Engagement permits).

Engagement (the contract)

The contract under which a Project is delivered for a Client. Carries rate card, billable / non-billable rules, ACL. TIME → INV pulls from here. Vietnamese-consultancy-specific.

Internal components

ComponentPath (planned)Responsibility
sync.rsservices/proj/src/sync.rsSync-engine — optimistic apply, conflict detection, server-canonical broadcast.
crdt.rsservices/proj/src/crdt.rsYjs document server via ywasm. Long-form fields (description, comment body) are Yjs; the rest is LWW with vector clock.
workflow.rsservices/proj/src/workflow.rsPer-project workflow overlay on the closed status enum.
ai_blocker.rsservices/proj/src/ai_blocker.rsBlocker detection from comment stream. "blocked by", "waiting on", and dwell-time heuristics → CUO Notify.
ai_review.rsservices/proj/src/ai_review.rsEnd-of-cycle review draft generator. Pulls from issue changelog + comments; CUO-stamped persona; AM edits before sending.
ai_calibration.rsservices/proj/src/ai_calibration.rsEstimate vs actual hours per Member per task class. Surfaces calibration drift to Member's dashboard.
brain_ingest.rsservices/proj/src/brain_ingest.rsLayer 3 ingestion of issues + comments ((FR pending)). Body content gates per ACL.
acl.rsservices/proj/src/acl.rsPer-task / per-engagement ACL. Private engagements not visible to non-engaged Members ((FR pending)).
autoroll.rsservices/proj/src/autoroll.rsEnd-of-cycle auto-roll. Incomplete issues move to next cycle if team config permits ((FR pending)).
nats_fanout.rsservices/proj/src/nats_fanout.rsNATS JetStream publisher / subscriber for mutation fan-out.
search.rsservices/proj/src/search.rsPGroonga query builder; VN bigram tokenisation.
graphql.rsservices/proj/src/graphql.rsFederated subgraph; @key(fields:"id") on every primitive.
mcp.rsservices/proj/src/mcp.rsMCP tool catalogue exported to CUO.
i18n.rsservices/proj/src/i18n.rsvi + en string tables; locale resolved from JWT claim; status names localised.
migrations/services/proj/migrations/sqlx migrations with RLS on every table.
4

Data model

The canonical state is PostgreSQL with row-level security by tenant_id and, for private engagements, additional ACL enforcement by engagement_id membership. Long-form fields are Yjs document snapshots stored as bytea blobs; the active CRDT state lives in Redis for the duration of a session.

erDiagram TENANT ||--o{ ENGAGEMENT : "has" ENGAGEMENT ||--o{ PROJECT : "delivered as" ENGAGEMENT ||--o{ RATE_CARD : "carries" PROJECT ||--o{ ISSUE : "contains" PROJECT ||--o{ CYCLE : "has" CYCLE ||--o{ ISSUE : "schedules" ISSUE ||--o{ COMMENT : "has" ISSUE ||--o{ DEPENDENCY : "depends on" ISSUE ||--o{ LABEL_ASSIGNMENT : "tagged" ISSUE ||--o| ASSIGNEE : "assigned to" ISSUE ||--o{ ATTACHMENT : "has" ISSUE ||--o{ TIME_ENTRY : "logged against" PROJECT ||--o{ WORKFLOW : "uses" WORKFLOW ||--o{ WORKFLOW_STATE : "defines" ISSUE ||--o{ HISTORY_EVENT : "audited" ISSUE ||--o{ BRAIN_LINK : "cites" CYCLE ||--o| CYCLE_REVIEW : "summarised" TENANT { uuid id PK string slug } ENGAGEMENT { uuid id PK uuid tenant_id FK uuid client_account_id FK "→ CRM" string name date start_date date end_date string acl_mode "open | restricted" string billing_mode "T&M | fixed-fee | retainer" } RATE_CARD { uuid id PK uuid engagement_id FK string role "senior | mid | junior | architect" int rate_vnd_per_hour int rate_usd_per_hour bool billable_default } PROJECT { uuid id PK uuid engagement_id FK string name string status "planned | active | paused | completed" bool client_visible timestamp created_at } CYCLE { uuid id PK uuid project_id FK int number date start_date date end_date int velocity_target bool autoroll_enabled } ISSUE { uuid id PK uuid project_id FK uuid cycle_id FK "nullable (backlog)" string code "PROJ-1234" string title bytea description_yjs "Yjs doc snapshot" string status "backlog|todo|in-progress|in-review|done|cancelled" string priority "urgent|high|medium|low|none" uuid assignee_id FK int estimate_hours int actual_hours uuid parent_issue_id FK "nullable" timestamp created_at timestamp updated_at uuid created_by FK } COMMENT { uuid id PK uuid issue_id FK uuid author_id FK bytea body_yjs "Yjs doc" timestamp created_at } DEPENDENCY { uuid from_issue_id FK uuid to_issue_id FK string kind "blocks | relates_to | duplicates" } LABEL_ASSIGNMENT { uuid issue_id FK string label } ATTACHMENT { uuid id PK uuid issue_id FK string filename string s3_key bigint size_bytes } TIME_ENTRY { uuid id PK uuid issue_id FK uuid member_id FK int minutes date entry_date bool billable string source "TIME" } WORKFLOW { uuid id PK uuid project_id FK string name } WORKFLOW_STATE { uuid id PK uuid workflow_id FK string code string display_name_vi string display_name_en int order_idx string category "todo | in-progress | done | cancelled" } HISTORY_EVENT { uuid id PK uuid issue_id FK uuid actor_id FK string kind "create | status | assign | comment | …" string from_value string to_value timestamp ts string brain_chain } BRAIN_LINK { uuid issue_id FK string brain_path "memories/decisions/…" string relation "cites | implements | supersedes" } CYCLE_REVIEW { uuid cycle_id PK string draft_markdown string sent_markdown timestamp generated_at timestamp sent_at }

Status enum + workflow overlay

The status enum is closed (backlog · todo · in-progress · in-review · done · cancelled). Per-project workflows can rename and re-order the visible states but always map back to a category in the closed set. The category is what drives velocity calculation, auto-roll, and BRAIN ingestion.

Statusvi nameen nameCategoryVelocity counts?
backlogTồn đọngBacklogtodono
todoCần làmTodotodono
in-progressĐang làmIn progressin-progressyes
in-reviewĐang reviewIn reviewin-progressyes
doneHoàn thànhDonedoneyes
cancelledHuỷ bỏCancelledcancelledno
5

API surface

Four surfaces: a WebSocket sync-engine for the SPA, a GraphQL federated subgraph for cross-module queries, an admin REST for migration and bulk import, and an MCP tool catalogue for CUO. All four share the same RBAC predicate.

GraphQL subgraph (federated)

extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@key", "@requiresScopes"])

type Engagement @key(fields: "id") {
  id: ID!
  name: String!
  clientAccountId: ID!
  startDate: Date!
  endDate: Date
  billingMode: BillingMode!
  projects: [Project!]!
  rateCards: [RateCard!]!
}

type Project @key(fields: "id") {
  id: ID!
  engagementId: ID!
  name: String!
  status: ProjectStatus!
  clientVisible: Boolean!
  cycles(active: Boolean): [Cycle!]!
  issues(filter: IssueFilter, limit: Int = 50, cursor: String): IssueConnection!
}

type Cycle @key(fields: "id") {
  id: ID!
  projectId: ID!
  number: Int!
  startDate: Date!
  endDate: Date!
  velocityTarget: Int
  issues: [Issue!]!
  review: CycleReview
}

type Issue @key(fields: "id") {
  id: ID!
  code: String!
  title: String!
  description: String!    # rendered from Yjs doc
  status: IssueStatus!
  priority: IssuePriority!
  assignee: Subject
  estimateHours: Int
  actualHours: Int
  cycleId: ID
  parent: Issue
  children: [Issue!]!
  dependencies: [Dependency!]!
  comments: [Comment!]!
  labels: [String!]!
  attachments: [Attachment!]!
  brainLinks: [BrainLink!]!
  history: [HistoryEvent!]!
}

enum IssueStatus { BACKLOG TODO IN_PROGRESS IN_REVIEW DONE CANCELLED }
enum IssuePriority { URGENT HIGH MEDIUM LOW NONE }
enum ProjectStatus { PLANNED ACTIVE PAUSED COMPLETED }
enum BillingMode { T_AND_M FIXED_FEE RETAINER }

type Mutation {
  createIssue(input: CreateIssueInput!): Issue!
    @requiresScopes(scopes: [["proj.write"]])
  updateIssue(id: ID!, patch: IssuePatch!): Issue!
  assignIssue(id: ID!, assigneeId: ID!): Issue!
  moveIssueToCycle(id: ID!, cycleId: ID): Issue!
  addComment(issueId: ID!, body: String!): Comment!
  closeCycle(cycleId: ID!): CycleReview!
    @requiresScopes(scopes: [["proj.cycle.close"]])
}

Sync-engine WebSocket protocol

// Client → server: optimistic mutation
{
  "op": "issue.update",
  "client_seq": 12834,
  "tenant_id": "01HZ…",
  "issue_id": "01HZ…",
  "patch": { "status": "in-progress", "assignee_id": "01HZ…" },
  "vector_clock": { "spa-stephen-laptop": 47 }
}

// Server → all room subscribers: canonical state
{
  "op": "issue.state",
  "server_seq": 982341,
  "issue_id": "01HZ…",
  "state": { "status": "in-progress", "assignee_id": "01HZ…", "updated_at": "…" },
  "vector_clock": { "spa-stephen-laptop": 47, "server": 982341 }
}

// Server → client (conflict, rebase needed)
{
  "op": "rebase",
  "client_seq": 12834,
  "server_state": { … },
  "reason": "concurrent_modification"
}

MCP tool catalogue

Tool nameInputsOutputsAnnotations
cyberos.proj.list_issuesproject_id?, cycle_id?, assignee_id?, status?Issue[]readonly · scope=proj.read
cyberos.proj.create_issueCreateIssueInputIssuescope=proj.write
cyberos.proj.update_issueid, patchIssuescope=proj.write
cyberos.proj.assign_issueid, assignee_idIssuescope=proj.write
cyberos.proj.add_commentissue_id, bodyCommentscope=proj.write
cyberos.proj.move_to_cycleid, cycle_idIssuescope=proj.write
cyberos.proj.close_cyclecycle_idCycleReview (draft)destructive · human-confirm · scope=proj.cycle.close
cyberos.proj.detect_blockersproject_id?Issue[] + reasonsreadonly
cyberos.proj.estimate_calibrationmember_id, rangeCalibration statsreadonly
6

Key flows

Flow 1 — Create an issue (sync-engine)

sequenceDiagram autonumber participant U as Member SPA participant WS as WebSocket gateway participant S as Sync engine participant W as Workflow validator participant ACL as ACL check participant PG as PostgreSQL participant N as NATS JetStream participant B as BRAIN audit participant OTHER as Other connected SPAs U->>U: optimistic local insert · status=todo U->>WS: op:issue.create · client_seq:N WS->>S: dispatch S->>ACL: check (subject, action:create, project_id) ACL-->>S: allow S->>W: validate status transition W-->>S: ok S->>PG: INSERT issue + history_event S->>B: proj.issue_create (chained audit row) S->>N: publish proj.issue.{project_id} N->>WS: fan-out WS->>OTHER: op:issue.state · server_seq:M WS-->>U: op:issue.state (canonical) Note over U: local optimistic state reconciled with server-canonical

Flow 2 — Concurrent edit + conflict-free merge via Yjs

sequenceDiagram autonumber participant A as Member A (offline) participant B as Member B (online) participant Y as Yjs document server participant PG as PostgreSQL participant N as NATS A->>A: edit description offline (vector clock advances) B->>Y: op:issue.description.update (online) Y->>PG: snapshot description_yjs Y->>N: publish issue.description.{id} Note over A: A comes back online A->>Y: sync (vector clock diff) Y->>Y: merge A's edits + B's edits (CRDT) Y->>PG: snapshot merged Yjs doc Y->>N: publish merged state Y-->>A: rebased state Y-->>B: rebased state Note over Y: no conflict resolution prompt — CRDT semantics handle it

Yjs handles the description / comment-body fields. Scalar fields (status, priority, assignee) use last-write-wins with the server's vector clock as the tiebreaker.

Flow 3 — AI cycle-review draft at cycle close

sequenceDiagram autonumber participant CL as Cron / Cycle close trigger participant S as Sync engine participant A as AI review generator participant AG as ⚡ AI Gateway participant CU as 🎯 CUO persona participant PG as PostgreSQL participant N as Notify (CHAT) to AM participant AM as Account Manager participant B as BRAIN audit CL->>S: cycle.end_date reached S->>A: assemble inputs (issues, comments, time entries, velocity) A->>AG: chat.completions (persona=CUO/COO-skill, cycle review template) AG-->>A: draft markdown A->>PG: cycle_review.draft_markdown = draft A->>N: ping AM "draft ready" A->>B: proj.cycle_review_draft AM->>S: edit draft AM->>S: send to client (via EMAIL) S->>B: proj.cycle_review_sent

Flow 4 — Blocker detection from comments

sequenceDiagram autonumber participant U as Commenter participant S as Sync engine participant BD as Blocker detector participant CU as 🎯 CUO participant N as Notify participant B as BRAIN audit U->>S: add_comment "blocked by waiting on client decision" S->>BD: classify comment BD->>BD: pattern match: "blocked by", "waiting on", dwell time > 48h BD-->>S: flag {kind:"external_blocker", confidence:0.91} S->>CU: enqueue notify to assigner CU->>N: ping assigner with issue link S->>B: proj.blocker_detected

Flow 5 — Estimate calibration nightly batch

sequenceDiagram autonumber participant CR as Nightly cron participant CAL as Calibration engine participant PG as PostgreSQL participant BR as 🧠 BRAIN write participant DASH as Member dashboard CR->>CAL: run for tenant CAL->>PG: SELECT estimate, actual hours per member per task_class PG-->>CAL: rows CAL->>CAL: compute ratio, drift, 7-day moving avg CAL->>BR: write calibration summary (BRAIN Layer 3) CAL->>PG: write calibration_snapshot rows DASH->>PG: query → render Member's calibration chart
7

Issue lifecycle

An issue traverses six canonical states (the closed enum). The per-project workflow overlay may rename or re-order the visible states but the underlying category drives velocity, auto-roll, and BRAIN ingestion.

stateDiagram-v2 [*] --> Backlog: created (no cycle) Backlog --> Todo: scheduled into cycle Todo --> InProgress: assignee starts InProgress --> InReview: PR opened / requesting review InReview --> InProgress: reviewer requested changes InReview --> Done: approved + merged Todo --> Cancelled: discarded InProgress --> Cancelled: discarded Done --> [*] Cancelled --> [*] Backlog --> Cancelled: discarded from backlog

Auto-roll behaviour at cycle close

Issue status at cycle closeAuto-roll behaviourConfigurable?
todoRoll to next cycleyes ((FR pending))
in-progressRoll to next cycleyes
in-reviewRoll to next cycleyes
doneStay in original cycle (velocity counts)no
cancelledStay in original cycleno
backlog (unscheduled)Stay in backlogn/a
8

Functional Requirements

The CyberOS FR catalogue is being rebuilt one feature at a time via the open fr-author Agent Skill.

Previous FR enumerations were archived 2026-05-14 and are no longer reflected on this page. PRD/SRS narrative remains authoritative for the spec; specific FRs land here as they are re-authored.

9

Non-Functional Requirements

NFRs from PRD §11.2 that PROJ must satisfy. Cross-referenced at nfr-catalog.html#proj.

NFR IDConcernTargetMeasurement
N(FR pending)Issue update mutationp95 ≤ 120 ms server-sidehistogram via OBS
N(FR pending)Issue list view (50 issues)p95 ≤ 250 ms (network + render)SPA RUM
N(FR pending)WebSocket fan-out latency (mutation → other clients)p95 ≤ 200 ms within regionNATS lag metric
N(FR pending)BRAIN ingestion (issue create → indexed)p95 ≤ 5 sBRAIN ingest histogram
N(FR pending)Offline-edit merge correctness≥ 99.9%property-based test (10k random workloads)
N(FR pending)Vietnamese search recall (50-query corpus)≥ 90%quarterly eval
N(FR pending)Cycle-review draft acceptance rate≥ 60% by AMSPA telemetry
N(FR pending)Service availability (28-day)≥ 99.9%OBS SLO monitor
N(FR pending)Private-engagement isolation= 0 cross-leakCI test on every PR
N(FR pending)RLS coverage100% of tablesmigration-CI gate
N(FR pending)Mutation durability after WS ack= 0 dropped under crashchaos test + BRAIN walk
N(FR pending)WS connections per tenant per axum task≥ 5k sustainedk6 load test
10

Dependencies

PROJ depends on AUTH for every call, BRAIN for ingestion + audit, TIME for time entries, AI for CUO features, MCP for tool surfacing, and NATS for fan-out. It is depended on by INV (billable hours roll-up), CRM (deal → engagement link), KB (project docs), and PORTAL (client-visible Project view at P2+).

graph LR subgraph upstream ["PROJ depends on"] AUTH["🔐 AUTH"] BRAIN["🧠 BRAIN"] TIME["⏱ TIME"] AI["⚡ AI Gateway"] MCP["🔌 MCP"] NATS["📡 NATS JetStream"] OBS["👁 OBS"] end PROJ["📋 PROJ"] subgraph downstream ["PROJ is depended on by"] INV["🧾 INV
billable roll-up"] CRM["🤝 CRM
deal→engagement"] KB["📚 KB
project docs"] EMAIL["✉️ EMAIL
thread→issue"] PORTAL["Portal · P2"] end AUTH --> PROJ BRAIN --> PROJ TIME --> PROJ AI --> PROJ MCP --> PROJ NATS --> PROJ OBS --> PROJ PROJ --> INV PROJ --> CRM PROJ --> KB EMAIL --> PROJ PROJ --> PORTAL classDef shipped fill:#f5ede6,stroke:#45210e classDef planned fill:#fef6e0,stroke:#9c750a class BRAIN shipped class PROJ,AUTH,TIME,AI,MCP,NATS,OBS,INV,CRM,KB,EMAIL,PORTAL planned
11

Compliance scope

PROJ is not a regulator's first stop, but it sits inside the audit trail for delivery work and must satisfy ACL, audit, and DSAR obligations.

Regulation / standardArticle / clausePROJ feature that satisfies it
Vietnam PDPL (Law 91/2025)Art. 14 — DSARDSAR export includes every issue / comment a subject authored.
Vietnam Decree 13/2023Art. 17 — Processing logHISTORY_EVENT table is the processing log; every mutation writes one row.
GDPR (EU 2016/679)Art. 17 — Right to erasureSoft-tombstone on assignee, then DSAR-driven hard purge via BRAIN.
GDPRArt. 25 — Privacy by designPrivate-engagement ACL means non-engaged Members cannot see issue titles.
ISO/IEC 27001:2022A.8.2 — Privileged accessRBAC + per-engagement ACL gate write operations.
ISO/IEC 27001:2022A.8.16 — MonitoringBRAIN audit chain covers create / status / assign / comment events.
SOC 2 Type IICC6.1 — Logical accessRLS + ACL enforcement at every mutation.
SOC 2 Type IICC7.2 — System monitoringOBS traces + BRAIN audit cross-correlate.
12

Risk entries

PROJ-specific risks tracked in the risk register.

IDRiskLikelihoodImpactOwnerMitigation
R-PROJ-001Sync-engine state divergence between SPA and serverMediumHighCTOProperty-based tests (10k random workloads); server is canonical; client always rebases on conflict.
R-PROJ-002Yjs CRDT memory leak on long-running document sessionsMediumMediumCTOSnapshot to Postgres every 60 s; evict in-memory doc after 5 min idle; reload on demand.
R-PROJ-003Private-engagement leak via cross-tenant graph queryLowHighCSORLS + ACL CI gates; quarterly red-team review of GraphQL surface.
R-PROJ-004NATS JetStream backlog → WS clients see stale stateMediumMediumCTONATS depth alerted at > 1k msgs; SPA reconnect triggers full state refetch.
R-PROJ-005Auto-roll inflates next cycle and burns out teamMediumMediumCOOAuto-roll opt-in per project; AM sees roll-over count in cycle review draft.
R-PROJ-006Cycle-review draft hallucinates events that did not occurMediumMediumCOODraft is an editable suggestion, never auto-sent; AM is accountable for content.
R-PROJ-007Blocker detector false-positive spamMediumLowCOOConfidence threshold ≥ 0.85; user "not a blocker" feedback trains down.
R-PROJ-008BRAIN ingestion lag breaks cross-module searchLowMediumCDOp95 ≤ 5 s SLO; backlog alarm pages CDO + CTO.
R-PROJ-009Calibration data weaponised in performance reviewMediumLowCHROCalibration visible only to Member + manager + CHRO; never client-visible; never used as sole performance signal (HR policy).
R-PROJ-010Vietnamese tokenisation regressions on PGroonga upgradeLowLowCTO50-query VN test corpus run on every PGroonga upgrade.
13

KPIs

PROJ rolls up 9 KPIs covering delivery cadence, sync-engine performance, AI feature efficacy, and Member experience.

KPIFormulaSourceTarget
Cycle velocity stabilitystddev(completed_points) / meancycle_review≤ 0.25 (steady)
Issue update mutation p95histogramOBS≤ 120 ms
WS fan-out lag p95histogramNATS≤ 200 ms
Auto-rolled issues per cyclecountcycle_reviewtracked; alert if > 30%
AI cycle-review acceptancesent / draftedSPA telemetry≥ 60%
Blocker-detection precisiontrue_positive / flaggeduser feedback≥ 80%
BRAIN ingestion lag p95histogramBRAIN≤ 5 s
Estimate calibration driftactual / estimate − 1calibration_snapshottracked per Member
Offline-merge incidentsconflicts / monthBRAIN audittracked; expect < 5 / mo
14

RACI matrix

PROJ is owned by the COO seat. Today (COO vacant), CEO is interim accountable; CTO owns engineering; Account Managers own per-engagement workflow configuration.

ActivityCEOCTOCOOCHROCDOAM
Service design + specARCICC
Sync-engine implementationIA/RIIII
Engagement / workflow configICAIIR
Cycle review sendIICIIA/R
Calibration policy (HR)CICA/RII
BRAIN ingestion reviewICIIA/RI
Private-engagement ACL auditCRAIII
VN localisation reviewCCA/RIIC
Incident responseARRICI

R Responsible · A Accountable · C Consulted · I Informed.

15

Planned CLI surface

A single admin CLI cyberos-proj for tenant operators and an SDK for scripted bulk edits.

1. Create an Engagement

$ cyberos-proj engagement create \
    --tenant cyberskill \
    --client-account acme-vn \
    --name "ACME Q3 platform build" \
    --billing-mode T_AND_M \
    --start 2026-07-01

[engagement created]
  id:        01HZK2…
  client:    acme-vn
  billing:   T_AND_M
  audit:     brain seq=15014 chain=1f2e…3d4c

2. Create a Cycle and bulk-create Issues

$ cyberos-proj cycle create --project pj-acme-platform --number 7 --start 2026-08-04 --weeks 2
[cycle created] id=01HZK3… number=7 dates=2026-08-04 / 2026-08-17

$ cyberos-proj issue bulk-create --cycle 01HZK3… --file backlog.yaml
[bulk-create] parsed 12 issues
[bulk-create] PROJ-1247 "Auth integration" → assignee=linh@…
[bulk-create] PROJ-1248 "Migration runbook" → assignee=tu@…
[bulk-create] … 10 more
[bulk-create] 12 created · 0 errors
[audit]       brain seq=15018 chain=…

3. Close a Cycle (auto-generate review draft)

$ cyberos-proj cycle close --id 01HZK3…

[cycle close]   12 issues · 8 done · 3 rolled · 1 cancelled
[review draft]  generated via CUO/COO-skill
[review draft]  saved to ./cycle-7-review.md (124 lines)
[notify]        Account Manager pinged in CHAT
[audit]         brain seq=15042 chain=…

4. Move all in-progress issues to next cycle

$ cyberos-proj cycle rollover --from 01HZK3… --to 01HZK4… --status in-progress

[rollover] 3 issues moved
[rollover] PROJ-1249 · PROJ-1251 · PROJ-1255
[audit]    brain seq=15045 chain=…

5. Estimate calibration report

$ cyberos-proj calibration --member linh@cyberskill.world --range 90d

member: linh@cyberskill.world
task_class       estimates  actual_avg  ratio   trend
backend.feature  18         1.18×        +18%   ▲ improving
infra.config     9          0.96×         -4%   ▬ stable
qa.test_plan     12         1.42×        +42%   ▼ widening
overall          39         1.18×        +18%   ▲ improving

6. Export DSAR bundle

$ cyberos-proj dsar-export --subject linh@cyberskill.world --output linh-proj.zip

[dsar]   subject:   linh@cyberskill.world
[dsar]   issues:    218 (authored) · 612 (assigned)
[dsar]   comments:  1,894
[dsar]   history:   4,217
[dsar]   written:   linh-proj.zip (38 MB)

7. Replay sync-engine conflict for debug

$ cyberos-proj sync replay --tenant cyberskill --since 1h --filter "issue.update"

[replay]  47 mutations · 2 rebases · 0 lost
[rebase]  PROJ-1247 · client_seq=18 server_seq=98234 reason=concurrent_modification
[rebase]  PROJ-1255 · client_seq=22 server_seq=98241 reason=concurrent_modification
16

Phase status & estimates

Status
Planned
P1 · design phase
Est. LoC
~11,000
Rust + TS + Yjs
Planned tests
140+
incl. CRDT property-tests
External libs
~14
axum · sqlx · ywasm · NATS
CLI subcommands
~22 planned
cyberos-proj
P1 budget
~$80/mo
Fargate + RDS + Redis + NATS
CapabilityStatus
Four primitives (Issue / Cycle / Project / Engagement)planned · P1
Sync-engine WS + optimistic applyplanned · P1
Yjs CRDT for description / commentsplanned · P1
Per-engagement rate cardsplanned · P1
TIME ↔ PROJ ↔ INV chainplanned · P1
Auto-roll at cycle closeplanned · P1
AI blocker detectionplanned · P1
AI cycle-review draftplanned · P1
Estimate calibration reportplanned · P1
BRAIN Layer 3 ingestionplanned · P1
Per-task / per-engagement ACLplanned · P1
Vietnamese localisationplanned · P1
Dependency graph + cycle detectionplanned · P1
Custom workflow per projectplanned · P1+
Mobile (offline-first)planned · P3+
Client-visible PORTAL viewplanned · P2+
17

References

  • PRD §9.5 — PROJ module: three primitives, sync-engine, AI features, FRs.
  • PRD §9.10 — TIME module (upstream dependency).
  • PRD §11.2 — NFRs that PROJ must satisfy.
  • SRS §4.5 — Formal (FR pending) through (FR pending) with verification methods.
  • Linear sync-engine — Tuomas Artman, "Scaling the Linear Sync Engine" (2023 talk).
  • Yjs CRDT frameworkgithub.com/yjs/yjs; ywasm Rust binding.
  • PGroonga — Postgres full-text search with CJK tokenisation.
  • NATS JetStream — message-streaming layer for WebSocket fan-out.
  • Apollo Federation v2.5 — subgraph composition spec.
  • Architecture context: infrastructure.html#proj.