Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions .github/workflows/cell-manifest-diff.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# SC-010:cell manifest 只读守门(AI-robust Hard)。
# 跑 `pnpm cell-manifest` 后断言 packages/devboard/src/manifest/ 无 diff —— 手改生成物即红。
name: cell-manifest-diff

on:
pull_request:
push:
branches: [develop]

jobs:
cell-manifest-readonly:
runs-on: ubuntu-latest
steps:
- name: Checkout gocell-web
uses: actions/checkout@v4
with:
path: gocell-web

# 后端 cells 源(公开仓库 ghbvf/gocell),checkout 为 gocell-web 的同级目录
# (cell-manifest 默认读 ../gocell/cells)。
- name: Checkout gocell (backend cells)
uses: actions/checkout@v4
with:
repository: ghbvf/gocell
path: gocell

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 11.4.0

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
cache-dependency-path: gocell-web/pnpm-lock.yaml

- name: Install (frozen lockfile)
run: pnpm install --frozen-lockfile
working-directory: gocell-web

- name: Regenerate cell manifest
run: pnpm cell-manifest
working-directory: gocell-web

# 守门:cell manifest 产物须与提交完全一致。
# 使用 `git status --porcelain` 同时覆盖 修改/新增/删除 三种漂移。
- name: Assert cell manifest is read-only (no manual edits / uncommitted)
run: |
if [ -n "$(git status --porcelain packages/devboard/src/manifest/)" ]; then
echo "::error::packages/devboard/src/manifest/ 与 cell-manifest 输出不一致(含未提交的新增/修改/删除)。请本地跑 \`pnpm cell-manifest\` 后提交。"
git status --porcelain packages/devboard/src/manifest/
git diff packages/devboard/src/manifest/
exit 1
fi
working-directory: gocell-web

# 守门第二段:生成物未漂移后,确认下游消费端类型未被破坏。
- name: Typecheck downstream consumers
run: pnpm -w typecheck
working-directory: gocell-web
7 changes: 7 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ jobs:
- name: Unit tests (with coverage)
run: pnpm test:coverage

# Boundary self-check (ESLint Node API) runs uninstrumented: under v8
# coverage the type-aware lint engine is ~12x slower and starves the
# vitest reporter RPC (onTaskUpdate timeout). Coverage adds no value for
# this enforcement spec, so it runs as its own fast step.
- name: Boundary self-check (no coverage)
run: pnpm test:boundary

- name: Build web
run: pnpm -F @gocell/web build

Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Codegen 生成物(只读,不 format)
packages/contracts/src/
packages/devboard/src/manifest/cells.generated.ts

# 构建产物
**/dist/
Expand Down
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@gocell/access": "workspace:*",
"@gocell/audit": "workspace:*",
"@gocell/config": "workspace:*",
"@gocell/devboard": "workspace:*",
"@gocell/request": "workspace:*",
"vue-i18n": "catalog:"
},
Expand Down
19 changes: 19 additions & 0 deletions apps/web/src/router/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,23 @@ describe('router layout fork', () => {
expect(m.meta.requiredAction).toBe('read')
expect(m.meta.requiredResource).toBe('flag')
})

it('renders /cells nested under the shell, behind the auth + PDP gates (Batch 5)', () => {
const m = router.resolve('/cells')
expect(m.name).toBe('cells')
expect(m.matched.length).toBeGreaterThanOrEqual(2)
expect(m.meta.requiresAuth).toBe(true)
expect(m.meta.requiredAction).toBe('read')
expect(m.meta.requiredResource).toBe('cell')
})

it('renders /cells/:id detail behind the auth + PDP gates, carrying the id param (Batch 5)', () => {
const m = router.resolve('/cells/accesscore')
expect(m.name).toBe('cell-detail')
expect(m.params.id).toBe('accesscore')
expect(m.matched.length).toBeGreaterThanOrEqual(2)
expect(m.meta.requiresAuth).toBe(true)
expect(m.meta.requiredAction).toBe('read')
expect(m.meta.requiredResource).toBe('cell')
})
})
18 changes: 18 additions & 0 deletions apps/web/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,24 @@ const routes: RouteRecordRaw[] = [
component: () => import('@gocell/config/views/flags'),
meta: { requiresAuth: true, requiredAction: 'read', requiredResource: 'flag' },
},
{
// Operate · Cells list (Batch 5). PDP-gated read on `cell` (fail-closed
// until /access/decide ships, BR-004). Data is the static build-time
// manifest derived from cell.yaml — no backend endpoint required.
path: 'cells',
name: 'cells',
component: () => import('@gocell/devboard/views/cells-list'),
meta: { requiresAuth: true, requiredAction: 'read', requiredResource: 'cell' },
},
{
// Operate · Cell detail (Batch 5). Same PDP gate; 12-tab inspector with
// the active tab carried in the `?tab=` query param. Own async chunk via
// the @gocell/devboard/views/cell-detail subpath export.
path: 'cells/:id',
name: 'cell-detail',
component: () => import('@gocell/devboard/views/cell-detail'),
meta: { requiresAuth: true, requiredAction: 'read', requiredResource: 'cell' },
},
],
},
]
Expand Down
11 changes: 11 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export default tseslint.config(
{
ignores: [
'packages/contracts/src/**', // codegen-生成物,不 lint
'packages/devboard/src/manifest/cells.generated.ts', // cell-manifest 生成物,不 lint
'**/dist/**',
'**/coverage/**',
'pnpm-lock.yaml',
Expand Down Expand Up @@ -396,6 +397,16 @@ export default tseslint.config(
},
},

// ── tools/cell-manifest vitest.config.ts ──────────────────────────────────
{
files: ['tools/cell-manifest/vitest.config.ts'],
languageOptions: {
parserOptions: {
project: path.resolve(__dirname, 'tools/cell-manifest/tsconfig.json'),
},
},
},

// ── Prettier: 放最后关闭格式类规则,交给 prettier ─────────────────────────
configPrettier,
)
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
"format": "prettier --write .",
"format:check": "prettier --check .",
"test": "vitest run",
"test:coverage": "vitest run --coverage",
"test:coverage": "vitest run --coverage --project '!root'",
"test:boundary": "vitest run --project root",
"test:e2e": "playwright test",
"codegen": "pnpm -F @gocell/codegen generate",
"cell-manifest": "pnpm -F @gocell/cell-manifest generate",
"prepare": "husky"
},
"lint-staged": {
Expand Down
175 changes: 175 additions & 0 deletions packages/core/src/i18n/messages/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,181 @@ const enUS = {
},
},
},
cells: {
list: {
title: 'Cells',
subtitle:
'Core objects. Every contract, dependency, task, config entry, and audit record belongs to a cell.',
searchLabel: 'Search cells',
searchPlaceholder: 'Search by id or name…',
domainAll: 'All',
domainFilterLabel: 'Filter by domain',
count: '{count} cells',
empty: 'No matching cells',
openCell: 'Open {name} details',
col: {
cell: 'Cell',
domain: 'Domain',
type: 'Type',
tier: 'Consistency',
owner: 'Owner team',
durability: 'Durability',
slices: 'Slices',
produces: 'Produces',
consumes: 'Consumes',
requires: 'Requires',
},
},
detail: {
back: 'Back to Cells',
notFound: 'Cell not found',
notFoundDesc: 'No cell with id "{id}" exists in the manifest.',
tablistLabel: 'Cell detail sections',
},
tabs: {
overview: 'Overview',
interfaces: 'Interfaces',
wiring: 'Wiring',
inventory: 'Inventory',
slices: 'Slices',
contracts: 'Contracts',
dependencies: 'Dependencies',
tasks: 'Tasks',
configuration: 'Configuration',
audit: 'Audit',
groups: 'Groups',
ai: 'AI',
},
overview: {
identity: 'Identity',
id: 'ID',
name: 'Name',
domain: 'Domain',
type: 'Type',
consistency: 'Consistency level',
durability: 'Durability',
owner: 'Owner team',
ownerRole: 'Role',
goStruct: 'Go struct',
schema: 'Primary schema',
lifecycle: 'Lifecycle',
health: 'Health',
runtime: 'Runtime',
},
interfaces: {
ispTitle: 'ISP sub-interfaces',
ispHint: 'Each cell exposes four isolated sub-interfaces (Interface Segregation Principle).',
identity: 'Identity',
identityDesc: 'Identity and ownership: id, owner team, Go struct.',
lifecycle: 'Lifecycle',
lifecycleDesc: 'Lifecycle, consistency level, and infrastructure dependencies.',
status: 'Status',
statusDesc: 'Health and runtime state (requires BR-001 health endpoint).',
inventory: 'Inventory',
inventoryDesc: 'Slices, contracts, and smoke tests inventory.',
produces: 'Produced contracts',
consumes: 'Consumed contracts',
contract: 'Contract',
role: 'Role',
noProduces: 'This cell does not produce any contracts.',
noConsumes: 'This cell does not consume any contracts.',
fields: {
id: 'ID',
owner: 'Owner team',
goStruct: 'Go struct',
lifecycle: 'Lifecycle',
consistency: 'Consistency level',
},
},
wiring: {
title: 'Infrastructure wiring',
requires: 'Required infrastructure',
l0deps: 'L0 dependencies',
infraToCell: 'Infrastructure → Cell',
none: 'No declared dependencies.',
hint: 'A cell seals its dependencies at construction time; the list below reflects runtime deps declared in cell.yaml.',
},
inventory: {
title: 'Inventory',
metric: 'Metric',
count: 'Count',
slices: 'Slices',
produces: 'Produced contracts',
consumes: 'Consumed contracts',
requires: 'Infrastructure dependencies',
smokeTests: 'Smoke tests',
contracts: 'Total contracts',
},
slices: {
title: 'Slices',
slice: 'Slice',
consistency: 'Consistency',
contracts: 'Contracts',
waivers: 'Waivers',
waiver: 'Waiver',
waiverExpires: 'Expires {date}',
none: 'This cell has no slices.',
},
contracts: {
title: 'Contracts',
produces: 'Produces',
consumes: 'Consumes',
contract: 'Contract',
role: 'Role',
none: 'No contracts.',
},
dependencies: {
title: 'Dependencies',
dependsOn: 'Depends on',
requiredBy: 'Required by',
infra: 'Infrastructure',
none: 'No cross-cell dependencies.',
hint: 'Dependencies are derived from the contract consumption relationships of slices.',
},
roles: {
serve: 'Serves',
call: 'Calls',
publish: 'Publishes',
subscribe: 'Subscribes',
},
badge: {
durable: 'Durable',
demo: 'Demo',
durableHint: 'Durable cell: state is persisted to the database.',
demoHint: 'Demo cell: state is not persisted.',
},
unavailable: {
label: '—',
title: 'Data unavailable',
runtime:
'Runtime metrics require a backend health endpoint (BR-001), which is not yet available.',
tasks: 'Task data source is not yet connected.',
groups: 'Smart grouping is a reserved capability (Batch 6).',
ai: 'AI capabilities are out of scope for this wave.',
},
configuration: {
title: 'Configuration',
tableLabel: 'Configuration entries',
loading: 'Loading configuration…',
error: 'Failed to load configuration.',
empty: 'No configuration entries for this cell.',
key: 'Key',
value: 'Value',
version: 'Version',
openFull: 'Open in configuration page',
},
audit: {
title: 'Audit',
tableLabel: 'Audit entries',
loading: 'Loading audit records…',
error: 'Failed to load audit records.',
empty: 'No audit records.',
action: 'Action',
actor: 'Actor',
time: 'Time',
openFull: 'Open in audit page',
},
},
errors: {
unknown: 'An unknown error occurred. Please try again.',
network: 'Network connection failed. Please check your connection.',
Expand Down
Loading
Loading