Compare commits
3 Commits
200-filame
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 15b21c3080 | |||
| 2d552c7ae8 | |||
| edf7646a18 |
@ -3,6 +3,9 @@ apps/platform/node_modules/
|
||||
apps/website/node_modules/
|
||||
apps/website/.astro/
|
||||
apps/website/dist/
|
||||
apps/website/playwright-report/
|
||||
apps/website/test-results/
|
||||
apps/website/blob-report/
|
||||
dist/
|
||||
build/
|
||||
vendor/
|
||||
|
||||
11
.github/agents/copilot-instructions.md
vendored
11
.github/agents/copilot-instructions.md
vendored
@ -205,6 +205,11 @@ ## Active Technologies
|
||||
- Repository-owned markdown and contract artifacts under `.specify/`, `specs/212-test-authoring-guardrails/`, and root documentation files; no product database persistence (212-test-authoring-guardrails)
|
||||
- PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `WorkspaceContext`, `OperateHubShell`, `EnsureFilamentTenantSelected`, `WorkspaceRedirectResolver`, `WorkspaceIntendedUrl`, `TenantPageCategory`, and `ResolvesPanelTenantContext` (199-global-context-shell-contract)
|
||||
- PostgreSQL unchanged plus existing Laravel session keys `current_workspace_id`, `workspace_intended_url`, and `workspace_last_tenant_ids`; no schema change planned (199-global-context-shell-contract)
|
||||
- Markdown governance artifacts in a PHP 8.4.15 / Laravel 12 / Filament v5 / Livewire v4 repository + `.specify/memory/constitution.md`, `docs/ui/operator-ux-surface-standards.md`, adjacent Specs 196 through 199, existing UI rule IDs `UI-SURF-001`, `ACTSURF-001`, `UI-HARD-001`, `UI-EX-001`, `UI-FIL-001`, `DECIDE-001`, and `UX-001` (200-filament-surface-rules)
|
||||
- Astro 6.0.0 templates + TypeScript 5.x (explicit setup in `apps/website`) + Astro 6, Tailwind CSS v4, custom Astro component primitives (shadcn-inspired), lightweight Playwright browser smoke tests (213-website-foundation-v0)
|
||||
- Static filesystem content, styles, and assets under `apps/website/src` and `apps/website/public`; no database (213-website-foundation-v0)
|
||||
- Markdown governance artifacts, JSON Schema plus logical OpenAPI planning contracts, and Bash-backed SpecKit scripts inside a PHP 8.4.15 / Laravel 12 / Filament v5 / Livewire v4 repository + `.specify/memory/constitution.md`, `.specify/templates/spec-template.md`, `.specify/templates/plan-template.md`, `.specify/templates/tasks-template.md`, `.specify/templates/checklist-template.md`, `.specify/README.md`, `docs/ui/operator-ux-surface-standards.md`, and Specs 196 through 200 (201-enforcement-review-guardrails)
|
||||
- Repository-owned markdown and contract artifacts under `.specify/` and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/201-enforcement-review-guardrails/`; no product database persistence (201-enforcement-review-guardrails)
|
||||
|
||||
- PHP 8.4.15 (feat/005-bulk-operations)
|
||||
|
||||
@ -239,8 +244,8 @@ ## Code Style
|
||||
PHP 8.4.15: Follow standard conventions
|
||||
|
||||
## Recent Changes
|
||||
- 199-global-context-shell-contract: Added PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `WorkspaceContext`, `OperateHubShell`, `EnsureFilamentTenantSelected`, `WorkspaceRedirectResolver`, `WorkspaceIntendedUrl`, `TenantPageCategory`, and `ResolvesPanelTenantContext`
|
||||
- 212-test-authoring-guardrails: Added Markdown for repository governance artifacts, JSON Schema plus logical OpenAPI for planning contracts, and Bash-backed SpecKit scripts already present in the repo + `.specify/memory/constitution.md`, `.specify/templates/spec-template.md`, `.specify/templates/plan-template.md`, `.specify/templates/tasks-template.md`, `.specify/templates/checklist-template.md`, `.specify/README.md`, `README.md`, and the existing Specs 206 through 211 governance vocabulary
|
||||
- 211-runtime-trend-recalibration: Added PHP 8.4.15 for repo-truth governance logic, Bash for repo-root wrappers, GitHub-compatible Gitea Actions workflow YAML under `.gitea/workflows/`, plus JSON Schema and logical OpenAPI for repository contracts + Laravel 12, Pest v4, PHPUnit 12, Filament v5, Livewire v4, Laravel Sail, Gitea Actions backed by `act_runner`, uploaded artifact bundles, and the existing `Tests\Support\TestLaneManifest`, `TestLaneBudget`, and `TestLaneReport` seams
|
||||
- 201-enforcement-review-guardrails: Added Markdown governance artifacts, JSON Schema plus logical OpenAPI planning contracts, and Bash-backed SpecKit scripts inside a PHP 8.4.15 / Laravel 12 / Filament v5 / Livewire v4 repository + `.specify/memory/constitution.md`, `.specify/templates/spec-template.md`, `.specify/templates/plan-template.md`, `.specify/templates/tasks-template.md`, `.specify/templates/checklist-template.md`, `.specify/README.md`, `docs/ui/operator-ux-surface-standards.md`, and Specs 196 through 200
|
||||
- 213-website-foundation-v0: Added Astro 6.0.0 templates + TypeScript 5.x (explicit setup in `apps/website`) + Astro 6, Tailwind CSS v4, custom Astro component primitives (shadcn-inspired), lightweight Playwright browser smoke tests
|
||||
- 200-filament-surface-rules: Added Markdown governance artifacts in a PHP 8.4.15 / Laravel 12 / Filament v5 / Livewire v4 repository + `.specify/memory/constitution.md`, `docs/ui/operator-ux-surface-standards.md`, adjacent Specs 196 through 199, existing UI rule IDs `UI-SURF-001`, `ACTSURF-001`, `UI-HARD-001`, `UI-EX-001`, `UI-FIL-001`, `DECIDE-001`, and `UX-001`
|
||||
<!-- MANUAL ADDITIONS START -->
|
||||
<!-- MANUAL ADDITIONS END -->
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -19,6 +19,9 @@
|
||||
/apps/website/node_modules
|
||||
/.pnpm-store
|
||||
/apps/website/.astro
|
||||
/apps/website/playwright-report
|
||||
/apps/website/test-results
|
||||
/apps/website/blob-report
|
||||
dist/
|
||||
build/
|
||||
coverage/
|
||||
|
||||
@ -7,6 +7,9 @@ apps/platform/node_modules/
|
||||
apps/website/node_modules/
|
||||
apps/website/.astro/
|
||||
apps/website/dist/
|
||||
apps/website/playwright-report/
|
||||
apps/website/test-results/
|
||||
apps/website/blob-report/
|
||||
vendor/
|
||||
apps/platform/vendor/
|
||||
*.log
|
||||
|
||||
@ -11,25 +11,46 @@ ## Important
|
||||
- `tasks.md`
|
||||
- `checklists/requirements.md`
|
||||
- Runtime-changing or test-affecting work MUST carry actual test-purpose classification (`Unit`, `Feature`, `Heavy-Governance`, `Browser`), affected lanes, fixture/default cost risks, heavy-family changes, escalation decisions, and minimal validation commands through the active `spec.md`, `plan.md`, and `tasks.md`.
|
||||
- Review-oriented checklists MUST surface lane fit, hidden defaults, heavy-family visibility, and runtime-budget follow-up before merge; lane upkeep belongs to the feature, not to a later cleanup pass.
|
||||
- Review-oriented checklists MUST surface nativity, shared-family boundaries, state-layer ownership, exception spread, proof depth, and close-out targeting before merge.
|
||||
- `.specify/` is the operational workflow. `docs/ui/operator-ux-surface-standards.md` remains a rule/reference document, not a second checklist or review process.
|
||||
|
||||
## Author Entry Point
|
||||
|
||||
Use the active feature's `spec.md`, `plan.md`, and `tasks.md` in that order.
|
||||
|
||||
1. Fill the spec's UI / Surface Guardrail Impact section once. If there is no operator-facing surface change, write a concise `N/A` and do not invent extra prose downstream.
|
||||
2. Turn that classification into plan-level handling modes, repository-signal treatment, required proof depth, and the named active feature PR close-out entry.
|
||||
3. Carry the same terms into tasks so implementation, review, definition-of-done, exception documentation, and smoke coverage all point at the same guardrail decision.
|
||||
|
||||
## Review Entry Point
|
||||
|
||||
Use the active feature's `spec.md`, `plan.md`, and `tasks.md` together with the generated checklist based on `.specify/templates/checklist-template.md`.
|
||||
|
||||
1. Confirm the spec names the affected validation lane(s) or a deliberate `N/A`, the test family impact, setup-cost impact, reviewer handoff, and any escalation outcome.
|
||||
2. Confirm the plan turns that into changed test types, narrowest proving commands, helper/default widening checks, and the note target for budget or trend drift.
|
||||
3. Apply the checklist and end with one explicit outcome: `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`.
|
||||
1. Confirm the spec names the guardrail impact once: native/custom classification, shared-family relevance, state-layer ownership, exception need, and any low-impact `N/A` path.
|
||||
2. Confirm the plan turns that into handling modes, repository-signal treatment, required test or smoke depth, and the named active feature PR close-out entry.
|
||||
3. Apply the checklist and end with both one review outcome class (`blocker`, `strong-warning`, `documentation-required-exception`, or `acceptable-special-case`) and one workflow outcome (`keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`).
|
||||
4. If a guarded surface or exception remains in scope, ensure the active feature PR close-out entry records the final note rather than leaving the decision in scattered review comments.
|
||||
|
||||
## Low-Impact Rule
|
||||
|
||||
- Docs-only or template-only work may answer `N/A` or `none`.
|
||||
- Do not force fake lane prose when no runtime or suite impact exists.
|
||||
- Do not force fake surface, lane, or exception prose when no operator-facing change or runtime impact exists.
|
||||
- Use the low-impact path to stay fast, not to hide a real guarded surface change.
|
||||
|
||||
## Escalation Rule
|
||||
|
||||
- Use `blocker` when fake-native drift, hidden host drift, unresolved state ownership, or missing exception boundaries remain.
|
||||
- Use `strong-warning` when the change can proceed only after the active workflow records the guardrail risk explicitly.
|
||||
- Use `documentation-required-exception` when default rules are intentionally relaxed and the bounded exception record is the remaining requirement.
|
||||
- Use `acceptable-special-case` only when the change already fits the declared guardrail contract.
|
||||
- Use `document-in-feature` for contained cost or drift that belongs in the active feature.
|
||||
- Use `follow-up-spec` only for recurring pain or structural lane or family changes.
|
||||
- Use `reject-or-split` when hidden test cost or wrong-lane scope is still unresolved.
|
||||
|
||||
## Close-Out Rule
|
||||
|
||||
- The named close-out target for guarded work is the active feature PR close-out entry `Guardrail / Exception / Smoke Coverage`.
|
||||
- That entry records the low-impact or representative scenario used, the outcome class, handling mode, workflow outcome, required tests or manual smoke, any exception boundary, duplicate-prompt notes, and any deferred automation.
|
||||
- If the change is genuinely low-impact, record a concise `N/A` note rather than fabricating guardrail spread.
|
||||
|
||||
The files `.specify/spec.md`, `.specify/plan.md`, `.specify/tasks.md` may exist as legacy references only.
|
||||
|
||||
@ -1,33 +1,19 @@
|
||||
<!--
|
||||
Sync Impact Report
|
||||
|
||||
- Version change: 2.4.0 -> 2.5.0
|
||||
- Version change: 2.5.0 -> 2.6.0
|
||||
- Modified principles:
|
||||
- Test Suite Governance Must Live In The Delivery Workflow
|
||||
(TEST-GOV-001): expanded into explicit test-impact disclosure,
|
||||
lane discipline, minimal-fixture defaults, heavy-family visibility,
|
||||
expensive-default bans, runtime-budget stewardship, review-stop
|
||||
rules, and escalation triggers
|
||||
- Governance review expectations: expanded to require test-purpose
|
||||
classification, explicit runtime-cost review, and visible review
|
||||
routine coverage in delivery artifacts
|
||||
- UI surface taxonomy and review expectations: expanded with native
|
||||
vs custom classification, shared-detail host ownership, named
|
||||
anti-patterns, and shell/page/detail state ownership review
|
||||
- Filament Native First / No Ad-hoc Styling (UI-FIL-001): expanded
|
||||
into explicit native-by-default, fake-native, shared-family, and
|
||||
exception-boundary language
|
||||
- Added sections: None
|
||||
- Removed sections: None
|
||||
- Templates requiring updates:
|
||||
- ✅ .specify/memory/constitution.md
|
||||
- ✅ .specify/templates/plan-template.md (lane-discipline and
|
||||
escalation-planning checks expanded)
|
||||
- ✅ .specify/templates/spec-template.md (test-purpose,
|
||||
lane-discipline, heavy-family, and escalation prompts expanded)
|
||||
- ✅ .specify/templates/tasks-template.md (task obligations expanded for
|
||||
classification, cheap defaults, review-stop rules, and runtime
|
||||
stewardship)
|
||||
- ✅ .specify/templates/checklist-template.md (review checklist guidance
|
||||
expanded for lane fit, heavy risk, and escalation)
|
||||
- ✅ .specify/README.md (SpecKit workflow expectations expanded for
|
||||
visible test-governance coverage)
|
||||
- ✅ README.md (developer workflow guidance expanded for lane
|
||||
discipline and runtime stewardship)
|
||||
- None in this docs-only constitution slice; enforcement remains
|
||||
deferred to Spec 201
|
||||
- Commands checked:
|
||||
- N/A `.specify/templates/commands/*.md` directory is not present in this repo
|
||||
- Follow-up TODOs: None
|
||||
@ -579,6 +565,24 @@ ##### Detail-first Operational Surface
|
||||
- Destructive actions: detail header or grouped header actions only, always with confirmation.
|
||||
- Row click and explicit View/Inspect: not applicable.
|
||||
|
||||
##### Native vs custom and shared-family classification
|
||||
- Every operator-facing surface MUST also classify whether it is a
|
||||
`Native Surface`, a `Custom Surface`, or a `Shared Detail Micro-UI`
|
||||
embedded inside a `Host`.
|
||||
- `Native Surface` means the primary interaction contract is expressed
|
||||
through Filament-native components or approved shared primitives.
|
||||
- `Custom Surface` means the operator need is materially richer than
|
||||
standard CRUD, overview, or report semantics and the deviation is
|
||||
justified through UI-EX-001.
|
||||
- `Shared Detail Micro-UI` means a repeated embedded review, evidence,
|
||||
or detail surface that appears in more than one host and must read as
|
||||
the same family wherever it appears.
|
||||
- `Host` means the page, resource, workbench, or detail surface that
|
||||
embeds a shared detail micro-UI and owns routing, authorization, the
|
||||
outer inspect/open model, and host-only actions.
|
||||
- A `Fake-Native Surface` is never an allowed classification. It is a
|
||||
violation class defined by UI-HARD-001 and UI-FIL-001.
|
||||
|
||||
#### Action Surface Discipline (ACTSURF-001)
|
||||
|
||||
Goal: actions across all surfaces MUST make the next sensible operator
|
||||
@ -649,6 +653,22 @@ ##### Utility / System surfaces
|
||||
- System or recovery status does not justify casual placement of
|
||||
destructive or governance-changing actions.
|
||||
|
||||
##### Shared detail families and one primary interaction model
|
||||
|
||||
- A shared detail micro-UI MUST define one family-level core
|
||||
interaction model before a second host extends it.
|
||||
- Hosts MAY vary framing, assist entry, surrounding navigation, or
|
||||
optional diagnostics only when the shared core remains recognizable
|
||||
and the variation is explicit.
|
||||
- The host owns page-level navigation, authorization, surrounding
|
||||
mutations, and dangerous actions. The shared family owns only the
|
||||
repeated read/inspect/view semantics that are intentionally common.
|
||||
- One user concern MUST NOT be split across two peer interaction
|
||||
models on the same page.
|
||||
- `Parallel Inspect Worlds` means the same concern is driven by two
|
||||
competing inspect, selected-record, or view-state owners. It is
|
||||
forbidden.
|
||||
|
||||
##### Action grouping and order
|
||||
|
||||
- Actions MUST be ordered by meaning, frequency, and risk.
|
||||
@ -718,7 +738,12 @@ ##### Review gate
|
||||
3. Is navigation cleanly separated from mutation?
|
||||
4. Are rare or risky actions removed from the primary plane?
|
||||
5. Is the hierarchy scanable in a few seconds?
|
||||
6. Is this a real special type or just an unordered exception?
|
||||
6. If this is a repeated detail family, what is shared core vs
|
||||
host-owned variation?
|
||||
7. Does one concern still have exactly one primary interaction model?
|
||||
8. Which layer owns the relevant truth: shell, page, or detail?
|
||||
9. Is any exception real, bounded, and named, or is it a hidden
|
||||
exception?
|
||||
|
||||
If those answers are not clear, the surface is non-conformant.
|
||||
|
||||
@ -737,6 +762,11 @@ ##### Primary inspect model
|
||||
- A surface MUST NOT offer row click, identifier click, and explicit View/Inspect for the same destination as parallel primary models.
|
||||
- CRUD / List-first and Read-only Registry / Report surfaces MUST provide an obvious one-click open path.
|
||||
- Queue / Review and History / Audit surfaces MUST use explicit Inspect rather than row-click navigation.
|
||||
- Inline detail, summary, or sidebar inspect MAY exist only as
|
||||
subordinate presentations of the same selected-record truth, not as a
|
||||
second inspect contract.
|
||||
- `Parallel Inspect Worlds` are forbidden even when each local variant
|
||||
looks individually reasonable.
|
||||
|
||||
##### Row-click semantics
|
||||
- Full-row click is the default for CRUD / List-first and Read-only Registry / Report surfaces.
|
||||
@ -755,6 +785,29 @@ ##### Action hierarchy
|
||||
- All other secondary actions MUST move to overflow.
|
||||
- Long-running workflow launches such as sync, compare, verify, generate, consent, setup, or retry SHOULD live in list headers or detail headers rather than in every row.
|
||||
|
||||
##### Native-by-default and fake-native drift
|
||||
- Standard form, filter, table, action, tab, badge, link, and overview
|
||||
work is `Native Surface` work by default when Filament or an existing
|
||||
shared primitive can express it.
|
||||
- A `Fake-Native Surface` is any surface that visually lives inside
|
||||
Filament but keeps a second HTML, GET, query, or Blade-request
|
||||
interaction contract for its primary behavior.
|
||||
- Simple report or overview pages with ordinary columns, filters, empty
|
||||
states, and navigation default to native table semantics.
|
||||
- `Filament Costume` means locally assembled markup imitates native
|
||||
Filament controls, badges, or actions even though native or shared
|
||||
primitives fit. It is forbidden.
|
||||
- `Blade Request UI` means the primary body-state contract depends on
|
||||
`request()`, GET forms, or manual query parsing inside an active
|
||||
Filament surface. It is forbidden unless a documented exception limits
|
||||
request input to initialization-only behavior.
|
||||
- `Hand-Rolled Simple Overview` means a simple report or overview is
|
||||
rebuilt as bespoke markup where native table/list/report semantics
|
||||
fit. It is forbidden.
|
||||
- `Hidden Exception` means a surface behaves like a custom or special
|
||||
case without naming an exception type and reason block. It is
|
||||
forbidden.
|
||||
|
||||
##### Destructive actions
|
||||
- Destructive actions MUST NOT appear inline beside the primary inspect interaction on standard CRUD, Config-lite, or Read-only Registry surfaces.
|
||||
- Destructive actions MUST live in overflow or the detail header.
|
||||
@ -802,6 +855,16 @@ ##### Row density and scanability
|
||||
- Standard CRUD rows MUST NOT carry more than one sentence of flowing prose.
|
||||
- Next-step prose belongs in detail, inspect, or queue surfaces, not in ordinary CRUD rows.
|
||||
|
||||
##### Shared-family and state-layer violations
|
||||
- `Host Drift` occurs when a host silently redefines a shared detail
|
||||
micro-UI's core zones, diagnostics contract, or primary view/inspect
|
||||
model. Host Drift is forbidden.
|
||||
- `State Layer Collapse` occurs when shell, page, or detail layers each
|
||||
claim the same active truth or restoration responsibility. It is
|
||||
forbidden.
|
||||
- A lower layer MAY format or reveal a higher-layer truth, but it MUST
|
||||
NOT quietly become the higher layer's authority.
|
||||
|
||||
##### Custom abstractions
|
||||
- Custom UI abstractions MAY document and validate, but they MUST NOT create declaration-only safety that diverges from real behavior.
|
||||
- Contract systems MUST NOT force placeholder UI.
|
||||
@ -812,6 +875,11 @@ #### Exception Model (UI-EX-001)
|
||||
|
||||
Only catalogued exception types are allowed. Every exception MUST be named in the spec, reference its exception type, include a reason block, be called out explicitly in the PR, and carry at least one dedicated test.
|
||||
|
||||
- A `Legitimate Exception` is a named, bounded deviation that states the
|
||||
product reason, the smallest custom behavior required, what remains
|
||||
standardized, which layer owns the relevant state, and what proof or
|
||||
review evidence keeps it from turning into a general permission slip.
|
||||
|
||||
##### Queue Decision Exception
|
||||
- Allowed when per-item decision-making is the real queue work.
|
||||
- Guardrails: Inspect remains available unless detail is already inline; irreversible decisions require confirmation; unrelated maintenance actions do not join the row.
|
||||
@ -832,6 +900,38 @@ ##### Cross-panel Canonical Route Exception
|
||||
- Allowed when only one canonical surface makes sense.
|
||||
- Guardrails: nouns stay stable; shell transition is explicit; back navigation is clear; scope signals remain truthful.
|
||||
|
||||
##### Legitimate Custom Surface Exception
|
||||
- Allowed when the operator need is materially richer than ordinary
|
||||
CRUD, overview, or simple report semantics, such as richer
|
||||
visualization, high-value diagnostic or review work, multi-zone shared
|
||||
detail micro-UI, shell-context-specific UI, or domain presentation
|
||||
that native primitives do not express cleanly.
|
||||
- Guardrails: the spec MUST state the product reason, the smallest
|
||||
custom behavior required, which native/shared primitives still apply,
|
||||
which layer owns the relevant state, and what remains standardized.
|
||||
|
||||
##### Nativity Exception
|
||||
- Allowed only when Filament-native or shared primitives cannot express
|
||||
the required semantics cleanly.
|
||||
- Guardrails: the exception MUST name the missing semantic, reject
|
||||
`Filament Costume`, `Blade Request UI`, and `Hand-Rolled Simple
|
||||
Overview` shortcuts, keep native/shared surrounding controls where
|
||||
they still fit, and MUST NOT invent a local status language.
|
||||
|
||||
##### Shared Detail Host Variation Exception
|
||||
- Allowed when a known shared detail micro-UI needs bounded host
|
||||
framing, assist entry, or optional-zone variation.
|
||||
- Guardrails: the host MUST NOT redefine the family core zones,
|
||||
next-step contract, diagnostics contract, or primary view/inspect
|
||||
model. Differences stay visibly host-scoped.
|
||||
|
||||
##### State-Layer Special-case Exception
|
||||
- Allowed when a page legitimately needs explicit requested, active,
|
||||
draft, inspect, or restorable roles beyond the simple default.
|
||||
- Guardrails: the owner layer MUST be explicit, the restorable subset
|
||||
MUST be explicit, any query role MUST be documented, and no lower
|
||||
layer may silently take over shell or page truth.
|
||||
|
||||
#### Filament UI — Action Surface Contract (NON-NEGOTIABLE)
|
||||
|
||||
For every new or modified Filament Resource, RelationManager, or Page:
|
||||
@ -842,6 +942,10 @@ #### Filament UI — Action Surface Contract (NON-NEGOTIABLE)
|
||||
- Accepted forms are `recordUrl()` row click, a primary linked column, or an explicit row action when the taxonomy requires Inspect.
|
||||
- CRUD / List-first, Config-lite, and Read-only Registry surfaces MUST NOT render a redundant View action when the same destination is already available through row click or identifier click.
|
||||
- Queue / Review and History / Audit surfaces MAY use a lone explicit Inspect action because context-preserving inspect is the primary interaction.
|
||||
- Simple report or overview pages with ordinary columns, filters,
|
||||
empty-state behavior, and navigation MUST be implemented as native
|
||||
table surfaces unless UI-EX-001 documents why a richer custom surface
|
||||
is required.
|
||||
- View/Detail MUST define header actions and MUST keep destructive actions grouped and confirmed.
|
||||
- View/Detail MUST be sectioned using Infolists, Sections, Cards, Tabs, or equivalent composable structure.
|
||||
- Create/Edit MUST provide consistent Save and Cancel UX.
|
||||
@ -850,6 +954,9 @@ #### Filament UI — Action Surface Contract (NON-NEGOTIABLE)
|
||||
- Standard CRUD and Read-only Registry rows MUST NOT exceed inspect/open plus one inline safe shortcut.
|
||||
- Queue / Review rows MAY expose inline decision actions only when allowed by UI-EX-001.
|
||||
- Everything else MUST move to `ActionGroup::make()` or the detail header.
|
||||
- Repeated embedded detail/evidence families MUST declare one shared
|
||||
core contract. Host-specific navigation, mutations, and destructive
|
||||
actions stay outside that shared core.
|
||||
- Bulk actions MUST be grouped via `BulkActionGroup` only when the surface has a real bulk use case.
|
||||
- Empty `ActionGroup` and `BulkActionGroup` are forbidden.
|
||||
- Destructive actions MUST NOT be primary and MUST require confirmation; typed confirmation MAY be required for large or high-risk bulk changes.
|
||||
@ -864,6 +971,12 @@ #### Filament UI — Action Surface Contract (NON-NEGOTIABLE)
|
||||
- Every spec MUST include both a UI/UX Surface Classification and a UI Action Matrix.
|
||||
- Every changed operator-facing surface MUST declare its broad
|
||||
action-surface class and the one most likely next operator action.
|
||||
- Every changed operator-facing surface MUST also declare whether it is
|
||||
a `Native Surface`, `Custom Surface`, or `Shared Detail Micro-UI`, and
|
||||
MUST name any exception type it relies on.
|
||||
- If a surface uses shell, page, or detail state beyond simple static
|
||||
rendering, the governing spec MUST name which layer owns the relevant
|
||||
requested, active, draft, inspect, and restorable state.
|
||||
- Custom action-surface contracts are legitimate only when they validate rendered behavior, not only declarations or slot counts.
|
||||
- A change is not Done unless the implemented interaction semantics conform to the declared surface type or an approved exception documents and tests the deviation.
|
||||
|
||||
@ -899,6 +1012,26 @@ #### Filament UI — Layout & Information Architecture Standards (UX-001)
|
||||
- Standard CRUD tables MUST stay scanable and MUST NOT rely on row prose to communicate next steps.
|
||||
- Critical operational truth that informs list decisions MUST be default-visible.
|
||||
|
||||
State ownership
|
||||
- `Global Context State` is shell-owned workspace, tenant, or tenantless
|
||||
truth. Context bars and shell partials may display it, but they MUST
|
||||
NOT invent second precedence or fallback logic.
|
||||
- `Page State` is page-owned filter, tab, mode, or selected-record truth
|
||||
that changes the current page result or workflow.
|
||||
- `Detail State` is embedded viewer or shared-family state inside one
|
||||
detail surface and remains subordinate to shell and page truth unless
|
||||
an approved exception says otherwise.
|
||||
- `Requested State` is route, query, or upstream input before validation.
|
||||
- `Active State` is the currently governing validated state.
|
||||
- `Draft State` is local pending state that is intentionally separate
|
||||
from the currently applied result state.
|
||||
- `Inspect State` is the selected-record or selected-detail focus that
|
||||
drives inline or same-page inspect.
|
||||
- `Restorable State` is the subset intentionally recreated by refresh,
|
||||
back, bookmark, or shared link.
|
||||
- `State Layer Collapse` is forbidden. Shell, page, and detail layers
|
||||
MUST NOT silently overwrite one another's authority.
|
||||
|
||||
Enforcement
|
||||
- Shared layout builders such as `MainAsideForm`, `MainAsideInfolist`, and `StandardTableDefaults` SHOULD be reused where available.
|
||||
- A change is not Done unless UX-001 is satisfied or an approved exception documents why not.
|
||||
@ -920,6 +1053,16 @@ ##### Core rule
|
||||
contextual references do not belong in the header; they belong directly
|
||||
at the affected field, status indicator, or relation.
|
||||
|
||||
##### Shared-family host discipline
|
||||
|
||||
- If a record or detail page embeds a shared detail micro-UI, the
|
||||
header remains host-owned.
|
||||
- Family-level view switches, tabs, diagnostics reveals, and inner
|
||||
assist controls belong inside the family, not as copied header
|
||||
buttons.
|
||||
- Host-specific navigation or mutations MAY appear in the header only
|
||||
when they are truly host-critical and do not redefine the family core.
|
||||
|
||||
##### Maximum one primary visible header action
|
||||
|
||||
- Each record/detail page MUST expose at most one clearly prioritized
|
||||
@ -1022,6 +1165,9 @@ ##### Reviewer heuristics
|
||||
- Pure navigation buttons in the header.
|
||||
- Danger actions beside normal actions without clear separation.
|
||||
- Rarely used administrative actions as visible standard buttons.
|
||||
- Shared-family view switches or host-only forks are exported into the
|
||||
main header instead of staying inside the family or contextual
|
||||
placement.
|
||||
- The header resembles an action stockpile instead of a focused
|
||||
workflow entry point.
|
||||
|
||||
@ -1120,8 +1266,12 @@ #### Enforcement Model (UI-REVIEW-001)
|
||||
actions are ordered, canonical collection route, canonical detail
|
||||
route, scope signals and their exact meaning, canonical noun,
|
||||
critical truth visible by default, workflow-vs-storage IA
|
||||
justification, attention-load reduction, and whether an exception
|
||||
type is used.
|
||||
justification, attention-load reduction, whether the surface is
|
||||
native, custom, or a shared detail family, what shared core vs host
|
||||
variation exists if relevant, which layer owns the relevant shell,
|
||||
page, and detail truth, which requested/active/draft/inspect/
|
||||
restorable roles exist, whether any fake-native or host-drift risk is
|
||||
present, and whether an exception type is used.
|
||||
- Missing any of those answers makes the spec incomplete.
|
||||
|
||||
PR review requirements
|
||||
@ -1136,8 +1286,10 @@ #### Enforcement Model (UI-REVIEW-001)
|
||||
promoted into primary navigation without justification, one case
|
||||
fragmented across multiple equal-rank pages, new automation that adds
|
||||
attention surfaces without reducing operator work, noisy default
|
||||
surfaces with no action/watch/reference hierarchy, or undocumented
|
||||
exceptions without dedicated tests.
|
||||
surfaces with no action/watch/reference hierarchy, `Filament Costume`,
|
||||
`Blade Request UI`, `Hand-Rolled Simple Overview`, `Hidden Exception`,
|
||||
`Host Drift`, `State Layer Collapse`, `Parallel Inspect Worlds`, or
|
||||
undocumented exceptions without dedicated tests.
|
||||
|
||||
Guard tests
|
||||
- Repository guards SHOULD validate: declared surface type, declared
|
||||
@ -1146,8 +1298,11 @@ #### Enforcement Model (UI-REVIEW-001)
|
||||
presence of explicit Inspect on Queue / Review and History / Audit
|
||||
surfaces, absence of empty `ActionGroup` or `BulkActionGroup`,
|
||||
correct placement of destructive actions, truthful scope signals,
|
||||
stable canonical nouns across shells, and dedicated tests for every
|
||||
approved exception.
|
||||
stable canonical nouns across shells, absence of fake-native primary
|
||||
controls where metadata says the surface is native, bounded shared
|
||||
family contracts where metadata says a family is reused, explicit
|
||||
state ownership where specs or metadata expose it, and dedicated
|
||||
tests for every approved exception.
|
||||
|
||||
#### Immediate Retrofit Priorities
|
||||
|
||||
@ -1200,6 +1355,13 @@ #### Appendix A - One-page Condensed Constitution
|
||||
- Destructive actions never sit openly beside inspect on standard lists.
|
||||
- Overflow is standardized per surface class and is never empty.
|
||||
- Bulk exists only when it is genuinely useful.
|
||||
- Standard forms, filters, tables, tabs, badges, links, and simple
|
||||
overviews are native-by-default.
|
||||
- Fake-native surfaces, hidden exceptions, host drift, and state-layer
|
||||
collapse do not ship.
|
||||
- Repeated detail micro-UIs define shared core and bounded host
|
||||
variation before a second host forks them.
|
||||
- Shell, page, and detail truth each have one owner.
|
||||
- Navigation and mutation do not share equal visual weight without
|
||||
explicit hierarchy.
|
||||
- Monitoring and workbench surfaces separate scope/context, selection,
|
||||
@ -1228,10 +1390,14 @@ #### Appendix B - Feature Review Checklist
|
||||
- Broad action-surface class is declared.
|
||||
- Detailed surface type is declared.
|
||||
- The one most likely next operator action is explicit.
|
||||
- The surface is classified correctly as native, custom, or shared
|
||||
family.
|
||||
- Primary inspect/open model is defined.
|
||||
- Row-click rule is decided.
|
||||
- View/Inspect is correctly present or correctly forbidden.
|
||||
- Edit-as-inspect is used only when allowed.
|
||||
- Fake-native shortcuts are absent or explicitly exception-gated.
|
||||
- Shared-family core vs host-owned variation is explicit where relevant.
|
||||
- Navigation and mutation are separated intentionally.
|
||||
- Secondary actions are grouped correctly.
|
||||
- Destructive actions are placed correctly.
|
||||
@ -1242,6 +1408,9 @@ #### Appendix B - Feature Review Checklist
|
||||
- Canonical nouns stay consistent.
|
||||
- Critical truth is visible.
|
||||
- Scanability is preserved.
|
||||
- Shell, page, and detail state owners are explicit.
|
||||
- Requested, active, draft, inspect, and restorable roles are explicit
|
||||
when the surface uses them.
|
||||
- Exceptions are documented and tested.
|
||||
- Header passes the 5-second scan rule (HDR-001).
|
||||
- No pure navigation in the header.
|
||||
@ -1261,6 +1430,11 @@ #### Appendix C - Red Flags for Future PRs
|
||||
attention load.
|
||||
- The surface creates more noise than priority.
|
||||
- Row click and View open the same destination.
|
||||
- A Filament-looking surface keeps its real primary contract in raw HTML,
|
||||
GET, or Blade request state.
|
||||
- A simple overview is rebuilt as bespoke markup without a real product
|
||||
reason.
|
||||
- A special surface exists only by history and lacks a named exception.
|
||||
- A row becomes a control center.
|
||||
- Archive or Delete sits openly beside View or Inspect on a standard list.
|
||||
- More menus or bulk menus are empty.
|
||||
@ -1274,6 +1448,10 @@ #### Appendix C - Red Flags for Future PRs
|
||||
actions as one flat header rail.
|
||||
- Critical health or operability truth is hidden by default.
|
||||
- A contract claims conformance while the rendered UI behaves differently.
|
||||
- A repeated detail surface quietly changes family core structure from
|
||||
one host to another.
|
||||
- Shell, page, and detail layers each claim the same truth.
|
||||
- One concern has two competing inspect or view-state owners.
|
||||
- Header has multiple equally weighted buttons without clear prioritization.
|
||||
- "Open X" navigation links placed in the header instead of at the related field.
|
||||
- Governance-changing actions sit casually beside the primary action without friction.
|
||||
@ -1294,14 +1472,36 @@ ### Filament Native First / No Ad-hoc Styling (UI-FIL-001)
|
||||
- If Filament already provides the required semantic element, feature code MUST use the Filament-native component instead of a locally assembled replacement.
|
||||
- Preferred native elements include `x-filament::badge`, `x-filament::button`, `x-filament::icon`, and Filament Forms, Infolists, Tables, Sections, Tabs, Grids, and Actions.
|
||||
|
||||
Native-by-default classification
|
||||
- `Native Surface` means the primary interaction contract is built from
|
||||
Filament-native components or approved shared primitives.
|
||||
- Standard forms, filters, tables, tabs, badges, links, and simple
|
||||
overviews default to `Native Surface` status.
|
||||
- `Custom Surface` is allowed only through UI-EX-001 when the operator
|
||||
need is richer than standard CRUD, overview, or report semantics.
|
||||
- `Fake-Native Surface` is forbidden: a surface that looks native but
|
||||
keeps a second HTML, GET, query, or Blade-request contract for the
|
||||
same primary interaction.
|
||||
|
||||
Forbidden local replacements
|
||||
- Feature code MUST NOT hand-build badges, pills, status chips, alert cards, or action buttons from raw `<span>`, `<div>`, or `<button>` markup plus Tailwind classes when Filament-native or shared project primitives can express the same meaning.
|
||||
- Feature code MUST NOT introduce page-local visual status languages for status, risk, outcome, drift, trust, importance, or severity.
|
||||
- Feature code MUST NOT make local color, border, rounding, or emphasis decisions for semantic UI states using ad-hoc classes such as `bg-danger-100`, `text-warning-900`, `border-dashed`, or `rounded-full` when the same state can be expressed through Filament props or shared primitives.
|
||||
- `Filament Costume` is forbidden: locally assembled markup that merely
|
||||
imitates native Filament controls, badges, or actions.
|
||||
- `Blade Request UI` is forbidden: request-driven body state or GET-form
|
||||
control rails as the primary interaction contract inside an active
|
||||
Filament surface.
|
||||
- `Hand-Rolled Simple Overview` is forbidden: bespoke overview/report
|
||||
shells where a native table/list surface fits the job.
|
||||
|
||||
Shared primitive before local override
|
||||
- If the same UI pattern can recur, it MUST use an existing shared primitive or introduce a new central primitive instead of reassembling the pattern inside a Blade view.
|
||||
- Central badge and status catalogs remain the canonical source for status semantics; local views MUST consume them rather than re-map them.
|
||||
- If the same custom detail, evidence, or review surface appears in
|
||||
more than one host, it becomes a `Shared Detail Micro-UI` and MUST
|
||||
define shared core vs host variation before another host reassembles
|
||||
it locally.
|
||||
|
||||
Upgrade-safe preference
|
||||
- Update-safe, framework-native implementations take priority over page-local styling shortcuts.
|
||||
@ -1313,13 +1513,19 @@ ### Filament Native First / No Ad-hoc Styling (UI-FIL-001)
|
||||
- native Filament components cannot express the required semantics,
|
||||
- no suitable shared primitive exists,
|
||||
- and the deviation is justified briefly in code and in the governing spec or PR.
|
||||
- Approved exceptions MUST stay layout-neutral, use the minimum local classes necessary, and MUST NOT invent a new page-local status language.
|
||||
- Approved exceptions MUST stay layout-neutral, use the minimum local
|
||||
classes necessary, MUST NOT invent a new page-local status language,
|
||||
and MUST say what remains standardized.
|
||||
- `Hidden Exception` is forbidden. Historical accident or local
|
||||
implementation convenience is not a valid substitute for UI-EX-001.
|
||||
|
||||
Review and enforcement
|
||||
- Every UI review MUST answer:
|
||||
- which native Filament element or shared primitive was used,
|
||||
- why an existing component was insufficient if an exception was taken,
|
||||
- and whether any ad-hoc status or emphasis styling was introduced.
|
||||
- whether the surface is native, custom, or a shared detail family,
|
||||
- and whether any ad-hoc status, emphasis styling, or fake-native
|
||||
contract was introduced.
|
||||
- UI work is not Done if it introduces ad-hoc status styling or framework-foreign replacement components where a native Filament or shared UI solution was viable.
|
||||
|
||||
### Incremental UI Standards Enforcement (UI-STD-001)
|
||||
@ -1367,4 +1573,4 @@ ### Versioning Policy (SemVer)
|
||||
- **MINOR**: new principle/section or materially expanded guidance.
|
||||
- **MAJOR**: removing/redefining principles in a backward-incompatible way.
|
||||
|
||||
**Version**: 2.5.0 | **Ratified**: 2026-01-03 | **Last Amended**: 2026-04-18
|
||||
**Version**: 2.6.0 | **Ratified**: 2026-01-03 | **Last Amended**: 2026-04-18
|
||||
|
||||
@ -5,39 +5,53 @@ # [CHECKLIST TYPE] Checklist: [FEATURE NAME]
|
||||
**Feature**: [Link to spec.md or relevant documentation]
|
||||
|
||||
**Note**: This checklist is generated by the `/speckit.checklist` command based on feature context and requirements.
|
||||
If the checklist covers runtime behavior or test-surface changes, use it to reach one explicit outcome: `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`.
|
||||
Low-impact docs-only or template-only work may mark runtime-only checks `N/A`, but should still leave one explicit outcome.
|
||||
If the checklist covers UI or surface work, use it to reach both one review
|
||||
outcome class (`blocker`, `strong-warning`,
|
||||
`documentation-required-exception`, or `acceptable-special-case`) and one
|
||||
workflow outcome (`keep`, `split`, `document-in-feature`,
|
||||
`follow-up-spec`, or `reject-or-split`). Low-impact docs-only or
|
||||
template-only work may mark runtime-only checks `N/A`, but should still
|
||||
leave one explicit workflow outcome and one note explaining why no
|
||||
guardrail spread exists.
|
||||
|
||||
## Lane Fit
|
||||
## Applicability And Low-Impact Gate
|
||||
|
||||
- [ ] CHK001 The chosen validation lane is the narrowest lane or lane mix that proves the change.
|
||||
- [ ] CHK002 The test stays in the smallest honest family (`Unit`, `Feature`, `Heavy-Governance`, `Browser`) and does not hide broader purpose behind a narrow label.
|
||||
- [ ] CHK001 The change explicitly says whether an operator-facing surface or guardrail workflow surface is affected; low-impact `N/A` handling is used once and not contradicted elsewhere.
|
||||
- [ ] CHK002 The spec, plan, and task artifacts carry forward the same native/custom classification, shared-family relevance, state-layer ownership, and exception need without inventing second wording.
|
||||
|
||||
## Breadth And Cost
|
||||
## Native, Shared-Family, And State Ownership
|
||||
|
||||
- [ ] CHK003 The changed or added test is no broader than the behavior it proves.
|
||||
- [ ] CHK004 Any database, Livewire, Filament, or browser surface is justified over a narrower alternative.
|
||||
- [ ] CHK005 Shared helpers, factories, seeds, fixtures, and context defaults stay cheap by default; any widening is explicit and locally justified.
|
||||
- [ ] CHK003 The surface remains native/shared-primitives first; fake-native controls, GET-form page-body interactions, and simple-overview replacements are not treated as harmless customization.
|
||||
- [ ] CHK004 Any shared-detail or shared-family surface keeps one shared contract, and any host variation is either folded back into that contract or explicitly bounded as an exception.
|
||||
- [ ] CHK005 Shell, page, detail, and URL/query state owners are named once and do not collapse into one another.
|
||||
- [ ] CHK006 The likely next operator action and the primary inspect/open model stay coherent with the declared surface class.
|
||||
|
||||
## Validation And Drift
|
||||
## Signals, Exceptions, And Test Depth
|
||||
|
||||
- [ ] CHK006 The minimal reviewer validation command is written explicitly and matches the declared lane.
|
||||
- [ ] CHK007 Any material budget, baseline, trend, or runtime-drift note is recorded in the active spec or PR.
|
||||
- [ ] CHK007 Any triggered repository signal is classified with one handling mode: `hard-stop-candidate`, `review-mandatory`, `exception-required`, or `report-only`.
|
||||
- [ ] CHK008 Any deviation from default rules includes a bounded exception record naming the broken rule, product reason, standardized parts, spread-control rule, and the active feature PR close-out entry.
|
||||
- [ ] CHK009 The required surface test profile is explicit: `shared-detail-family`, `monitoring-state-page`, `global-context-shell`, `exception-coded-surface`, or `standard-native-filament`.
|
||||
- [ ] CHK010 The chosen test family/lane and any manual smoke are the narrowest honest proof for the declared surface class, and `standard-native-filament` relief is used when no special contract exists.
|
||||
|
||||
## Escalation Outcome
|
||||
## Review Outcome
|
||||
|
||||
- [ ] CHK008 One explicit outcome is chosen: `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`.
|
||||
- [ ] CHK009 New heavy families, new browser coverage, revived expensive defaults, or material lane-cost shifts are not left implicit.
|
||||
- [ ] CHK011 One review outcome class is chosen: `blocker`, `strong-warning`, `documentation-required-exception`, or `acceptable-special-case`.
|
||||
- [ ] CHK012 One workflow outcome is chosen: `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`.
|
||||
- [ ] CHK013 The final note location is explicit: the active feature PR close-out entry for guarded work, or a concise `N/A` note for low-impact changes.
|
||||
|
||||
## Notes
|
||||
|
||||
- `keep`: current lane, family, and setup are justified.
|
||||
- `split`: scope is valid, but the test or helper spread should be narrowed before merge.
|
||||
- `document-in-feature`: the change is acceptable, but the cost or drift must be recorded in the active spec or PR.
|
||||
- `follow-up-spec`: recurring pain or structural lane or family changes need dedicated governance work.
|
||||
- `reject-or-split`: hidden cost, wrong lane, or unjustified breadth blocks merge as proposed.
|
||||
- `blocker`: the change conflicts with the declared surface contract or guardrail and cannot proceed as proposed.
|
||||
- `strong-warning`: the change may proceed only after the active workflow records the remaining guardrail risk explicitly.
|
||||
- `documentation-required-exception`: the change is valid only once a bounded exception and close-out note exist.
|
||||
- `acceptable-special-case`: the change is legitimate without extra escalation beyond ordinary documentation.
|
||||
- `keep`: the current scope, guardrail handling, and proof depth are justified.
|
||||
- `split`: the intent is valid, but the scope should narrow before merge.
|
||||
- `document-in-feature`: the change is acceptable, but the active feature must record the exception, signal handling, or proof notes explicitly.
|
||||
- `follow-up-spec`: the issue is recurring or structural and needs dedicated governance follow-up.
|
||||
- `reject-or-split`: hidden drift, unresolved exception spread, or wrong proof depth blocks merge as proposed.
|
||||
- Check items off as completed: `[x]`
|
||||
- Add comments or findings inline
|
||||
- Link to relevant resources or documentation
|
||||
- Items are numbered sequentially for easy reference
|
||||
- Reviewer-facing runtime checklists SHOULD stop merge when lane fit, hidden cost, heavy-family drift, or escalation handling is unclear.
|
||||
- Reviewer-facing checklists SHOULD stop merge when nativity, shared-family boundaries, state ownership, exception spread, test depth, or escalation handling is unclear.
|
||||
|
||||
@ -28,6 +28,21 @@ ## Technical Context
|
||||
**Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION]
|
||||
**Scale/Scope**: [domain-specific, e.g., 10k users, 1M LOC, 50 screens or NEEDS CLARIFICATION]
|
||||
|
||||
## UI / Surface Guardrail Plan
|
||||
|
||||
> **Fill for operator-facing or guardrail-relevant workflow changes. Docs-only or template-only work may use concise `N/A`. Copy the spec classification forward; do not rename or expand it here.**
|
||||
|
||||
- **Guardrail scope**: [no operator-facing surface change / changed surfaces / workflow-only guardrail change]
|
||||
- **Native vs custom classification summary**: [native / custom / mixed / N/A]
|
||||
- **Shared-family relevance**: [none / list affected shared families]
|
||||
- **State layers in scope**: [shell / page / detail / URL-query / none]
|
||||
- **Handling modes by drift class or surface**: [hard-stop-candidate / review-mandatory / exception-required / report-only / N/A]
|
||||
- **Repository-signal treatment**: [report-only / review-mandatory / exception-required / future hard-stop candidate / N/A]
|
||||
- **Special surface test profiles**: [standard-native-filament / shared-detail-family / monitoring-state-page / global-context-shell / exception-coded-surface / N/A]
|
||||
- **Required tests or manual smoke**: [functional-core / state-contract / exception-fallback / manual-smoke / N/A]
|
||||
- **Exception path and spread control**: [none / describe the named exception boundary]
|
||||
- **Active feature PR close-out entry**: [Guardrail / Exception / Smoke Coverage / N/A]
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
@ -89,6 +104,11 @@ ## Constitution Check
|
||||
selection actions, navigation, and object actions; risky or rare
|
||||
actions are grouped and ordered by meaning/frequency/risk; any special
|
||||
type or workflow-hub exception is explicit and justified
|
||||
- UI review workflow: native/custom classification, shared-family
|
||||
relevance, state-layer ownership, repository-signal treatment,
|
||||
exception path, and the active feature PR close-out entry stay
|
||||
explicit without duplicating the same decision across spec, plan,
|
||||
tasks, checklist, and close-out surfaces
|
||||
|
||||
## Test Governance Check
|
||||
|
||||
@ -101,10 +121,12 @@ ## Test Governance Check
|
||||
- **Fixture / helper / factory / seed / context cost risks**: [none / describe]
|
||||
- **Expensive defaults or shared helper growth introduced?**: [no / describe explicit opt-in path]
|
||||
- **Heavy-family additions, promotions, or visibility changes**: [none / describe]
|
||||
- **Surface-class relief / special coverage rule**: [standard-native relief / named special profile / N/A]
|
||||
- **Closing validation and reviewer handoff**: [What must be re-run, what reviewers should verify, and what exact proof command they should rely on]
|
||||
- **Budget / baseline / trend follow-up**: [none / describe]
|
||||
- **Review-stop questions**: [lane fit / breadth / hidden cost / heavy-family risk / escalation]
|
||||
- **Escalation path**: [none / document-in-feature / follow-up-spec / reject-or-split]
|
||||
- **Active feature PR close-out entry**: [Guardrail / Exception / Smoke Coverage / N/A]
|
||||
- **Why no dedicated follow-up spec is needed**: [Routine upkeep stays inside this feature unless recurring pain or structural lane changes justify a separate spec]
|
||||
|
||||
## Project Structure
|
||||
|
||||
@ -35,11 +35,23 @@ ## Spec Scope Fields *(mandatory)*
|
||||
- **Default filter behavior when tenant-context is active**: [e.g., prefilter to current tenant]
|
||||
- **Explicit entitlement checks preventing cross-tenant leakage**: [Describe checks]
|
||||
|
||||
## UI / Surface Guardrail Impact *(mandatory when operator-facing surfaces are changed; otherwise write `N/A`)*
|
||||
|
||||
Use this section to classify UI and surface risk once. If the feature does
|
||||
not change an operator-facing surface, write `N/A - no operator-facing surface
|
||||
change` here and do not invent duplicate prose in the downstream surface tables.
|
||||
|
||||
| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note |
|
||||
|---|---|---|---|---|---|---|
|
||||
| e.g. Tenant policies page | yes | Native Filament + shared primitives | none | page, detail | no | n/a |
|
||||
| e.g. Docs-only change | no | N/A | none | none | no | `N/A - repository workflow only` |
|
||||
|
||||
## Decision-First Surface Role *(mandatory when operator-facing surfaces are changed)*
|
||||
|
||||
If this feature adds or materially changes an operator-facing surface,
|
||||
fill out one row per affected surface. This role is orthogonal to the
|
||||
Action Surface Class / Surface Type below.
|
||||
Action Surface Class / Surface Type below. Reuse the exact surface names
|
||||
and classifications from the UI / Surface Guardrail Impact section above.
|
||||
|
||||
| Surface | Decision Role | Human-in-the-loop Moment | Immediately Visible for First Decision | On-Demand Detail / Evidence | Why This Is Primary or Why Not | Workflow Alignment | Attention-load Reduction |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
@ -50,7 +62,8 @@ ## UI/UX Surface Classification *(mandatory when operator-facing surfaces are ch
|
||||
If this feature adds or materially changes an operator-facing list, detail, queue, audit, config, or report surface,
|
||||
fill out one row per affected surface. Declare the broad Action Surface
|
||||
Class first, then the detailed Surface Type. Keep this table in sync
|
||||
with the Decision-First Surface Role section above.
|
||||
with the Decision-First Surface Role section above and avoid renaming the
|
||||
same surface a second time.
|
||||
|
||||
| Surface | Action Surface Class | Surface Type | Likely Next Operator Action | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type / Justification |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
@ -98,9 +111,12 @@ ## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
|
||||
- **New or expanded test families**: [none / describe]
|
||||
- **Fixture / helper cost impact**: [none / describe new defaults, factories, seeds, helpers, browser setup, provider setup, workspace or membership context, session state, etc.]
|
||||
- **Heavy-family visibility / justification**: [none / explain any heavy-governance or browser addition and how it remains explicit in naming, lane choice, and review]
|
||||
- **Special surface test profile**: [standard-native-filament / shared-detail-family / monitoring-state-page / global-context-shell / exception-coded-surface / N/A]
|
||||
- **Standard-native relief or required special coverage**: [ordinary feature coverage only / describe required tests or smoke checks]
|
||||
- **Reviewer handoff**: [What reviewers must confirm about lane fit, hidden cost, heavy-family visibility, and the exact proof command]
|
||||
- **Budget / baseline / trend impact**: [none / expected drift + follow-up]
|
||||
- **Escalation needed**: [none / document-in-feature / follow-up-spec / reject-or-split]
|
||||
- **Active feature PR close-out entry**: [Guardrail / Exception / Smoke Coverage / N/A]
|
||||
- **Planned validation commands**: [Exact minimal commands reviewers should run]
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
@ -46,6 +46,13 @@ # Tasks: [FEATURE NAME]
|
||||
- using source/domain terms only where same-screen disambiguation is required,
|
||||
- aligning button labels, modal titles, run titles, notifications, and audit prose to the same domain vocabulary,
|
||||
- removing implementation-first wording from primary operator-facing copy.
|
||||
**UI / Surface Guardrails**: If this feature adds or changes operator-facing surfaces or the workflow that governs them, tasks MUST include:
|
||||
- carrying forward the spec's native/custom classification, shared-family relevance, state-layer ownership, and exception need into implementation work without renaming the same decision,
|
||||
- classifying any triggered repository signals with one handling mode (`hard-stop-candidate`, `review-mandatory`, `exception-required`, or `report-only`),
|
||||
- adding explicit review or definition-of-done work when a guarded surface class, repository signal, or exception path is involved,
|
||||
- adding required tests or manual smoke for `shared-detail-family`, `monitoring-state-page`, `global-context-shell`, or `exception-coded-surface`, OR recording `standard-native-filament` relief when no special contract exists,
|
||||
- adding exception documentation and spread-control tasks whenever default surface rules are intentionally relaxed,
|
||||
- recording the active feature PR close-out entry with guardrail class, exception status, required tests/manual smoke, low-impact `N/A` use, and any deferred automation.
|
||||
**Operator Surfaces**: If this feature adds or materially refactors an operator-facing page or flow, tasks MUST include:
|
||||
- classifying each affected surface as Primary Decision, Secondary
|
||||
Context, or Tertiary Evidence / Diagnostics and keeping that role in
|
||||
@ -141,6 +148,7 @@ ## Test Governance Checklist
|
||||
- [ ] New or changed tests stay in the smallest honest family, and any heavy-governance or browser addition is explicit.
|
||||
- [ ] Shared helpers, factories, seeds, fixtures, and context defaults stay cheap by default; any widening is isolated or documented.
|
||||
- [ ] Planned validation commands cover the change without pulling in unrelated lane cost.
|
||||
- [ ] The declared surface test profile or `standard-native-filament` relief is explicit.
|
||||
- [ ] Any material budget, baseline, trend, or escalation note is recorded in the active spec or PR.
|
||||
|
||||
## Format: `[ID] [P?] [Story] Description`
|
||||
@ -287,6 +295,7 @@ ## Phase N: Polish & Cross-Cutting Concerns
|
||||
- [ ] TXXX [P] Additional unit tests (if requested) in tests/unit/
|
||||
- [ ] TXXX Security hardening
|
||||
- [ ] TXXX Proportionality cleanup: remove or collapse superseded layers introduced during implementation
|
||||
- [ ] TXXX Record the active feature PR close-out entry with guardrail class, exception status, proof depth, and deferred automation
|
||||
- [ ] TXXX Run quickstart.md validation
|
||||
|
||||
---
|
||||
|
||||
@ -1,9 +1,23 @@
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
const publicSiteUrl = process.env.PUBLIC_SITE_URL ?? 'https://tenantatlas.example';
|
||||
|
||||
export default defineConfig({
|
||||
output: 'static',
|
||||
site: publicSiteUrl,
|
||||
server: {
|
||||
host: true,
|
||||
port: 4321,
|
||||
},
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -9,9 +9,18 @@
|
||||
"scripts": {
|
||||
"dev": "astro dev --host 0.0.0.0 --port ${WEBSITE_PORT:-4321}",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview --host 0.0.0.0"
|
||||
"preview": "astro preview --host 0.0.0.0",
|
||||
"test": "playwright test",
|
||||
"test:smoke": "playwright test"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.59.1",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@types/node": "^24.7.2",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
29
apps/website/playwright.config.ts
Normal file
29
apps/website/playwright.config.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
const port = Number(process.env.WEBSITE_PORT ?? '4321');
|
||||
const baseURL = `http://127.0.0.1:${port}`;
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './tests/smoke',
|
||||
fullyParallel: true,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
reporter: [['list']],
|
||||
use: {
|
||||
baseURL,
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
},
|
||||
},
|
||||
],
|
||||
webServer: {
|
||||
command: `WEBSITE_PORT=${port} corepack pnpm dev`,
|
||||
port,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120000,
|
||||
},
|
||||
});
|
||||
@ -1,2 +1,3 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Sitemap: /sitemap.xml
|
||||
|
||||
35
apps/website/src/components/content/AudienceRow.astro
Normal file
35
apps/website/src/components/content/AudienceRow.astro
Normal file
@ -0,0 +1,35 @@
|
||||
---
|
||||
import SecondaryCTA from '@/components/content/SecondaryCTA.astro';
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import type { AudienceRowContent } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
item: AudienceRowContent;
|
||||
}
|
||||
|
||||
const { item } = Astro.props;
|
||||
---
|
||||
|
||||
<Card class="h-full">
|
||||
<p class="m-0 text-xs font-semibold uppercase tracking-[0.16em] text-[var(--color-brand)]">
|
||||
{item.audience}
|
||||
</p>
|
||||
<h3 class="mt-4 text-3xl font-semibold tracking-[-0.03em] text-[var(--color-ink-900)]">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">{item.description}</p>
|
||||
<ul class="mt-5 space-y-3 p-0">
|
||||
{
|
||||
item.bullets.map((bullet) => (
|
||||
<li class="list-none rounded-[1rem] bg-white/70 px-4 py-3 text-sm text-[var(--color-ink-800)]">
|
||||
{bullet}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
{item.cta && (
|
||||
<div class="mt-6">
|
||||
<SecondaryCTA cta={item.cta} />
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
23
apps/website/src/components/content/Callout.astro
Normal file
23
apps/website/src/components/content/Callout.astro
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import type { CalloutContent } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
content: CalloutContent;
|
||||
}
|
||||
|
||||
const { content } = Astro.props;
|
||||
const variant = content.tone === 'accent' ? 'accent' : content.tone === 'subtle' ? 'subtle' : 'default';
|
||||
---
|
||||
|
||||
<Card variant={variant}>
|
||||
{content.eyebrow && (
|
||||
<p class="m-0 text-xs font-semibold uppercase tracking-[0.16em] text-[var(--color-brand)]">
|
||||
{content.eyebrow}
|
||||
</p>
|
||||
)}
|
||||
<h3 class="mt-4 text-2xl font-semibold tracking-[-0.03em] text-[var(--color-ink-900)]">
|
||||
{content.title}
|
||||
</h3>
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">{content.description}</p>
|
||||
</Card>
|
||||
29
apps/website/src/components/content/ContactPanel.astro
Normal file
29
apps/website/src/components/content/ContactPanel.astro
Normal file
@ -0,0 +1,29 @@
|
||||
---
|
||||
import Button from '@/components/primitives/Button.astro';
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import type { CtaLink } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
cta: CtaLink;
|
||||
points: string[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { cta, points, title } = Astro.props;
|
||||
---
|
||||
|
||||
<Card variant="accent">
|
||||
<h3 class="m-0 text-3xl font-semibold tracking-[-0.03em] text-[var(--color-ink-900)]">{title}</h3>
|
||||
<ul class="mt-5 space-y-3 p-0">
|
||||
{
|
||||
points.map((point) => (
|
||||
<li class="list-none rounded-[1rem] bg-white/72 px-4 py-3 text-sm text-[var(--color-ink-800)]">
|
||||
{point}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
<div class="mt-6">
|
||||
<Button href={cta.href} variant={cta.variant ?? 'primary'}>{cta.label}</Button>
|
||||
</div>
|
||||
</Card>
|
||||
18
apps/website/src/components/content/DemoPrompt.astro
Normal file
18
apps/website/src/components/content/DemoPrompt.astro
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
|
||||
interface Props {
|
||||
description: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { description, title } = Astro.props;
|
||||
---
|
||||
|
||||
<Card>
|
||||
<p class="m-0 text-xs font-semibold uppercase tracking-[0.16em] text-[var(--color-brand)]">
|
||||
Conversation focus
|
||||
</p>
|
||||
<h3 class="mt-4 text-2xl font-semibold tracking-[-0.03em] text-[var(--color-ink-900)]">{title}</h3>
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">{description}</p>
|
||||
</Card>
|
||||
11
apps/website/src/components/content/Eyebrow.astro
Normal file
11
apps/website/src/components/content/Eyebrow.astro
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { class: className = '' } = Astro.props;
|
||||
---
|
||||
|
||||
<p class:list={['m-0 text-sm font-semibold uppercase tracking-[0.18em] text-[var(--color-brand)]', className]}>
|
||||
<slot />
|
||||
</p>
|
||||
32
apps/website/src/components/content/FeatureItem.astro
Normal file
32
apps/website/src/components/content/FeatureItem.astro
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import type { FeatureItemContent } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
item: FeatureItemContent;
|
||||
}
|
||||
|
||||
const { item } = Astro.props;
|
||||
---
|
||||
|
||||
<Card class="h-full">
|
||||
{item.eyebrow && (
|
||||
<p class="m-0 text-xs font-semibold uppercase tracking-[0.16em] text-[var(--color-brand)]">
|
||||
{item.eyebrow}
|
||||
</p>
|
||||
)}
|
||||
<h3 class="mt-4 text-2xl font-semibold tracking-[-0.03em] text-[var(--color-ink-900)]">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">{item.description}</p>
|
||||
{(item.meta || item.href) && (
|
||||
<div class="mt-5 flex flex-wrap items-center gap-3 text-sm">
|
||||
{item.meta && <span class="text-[var(--color-brand)]">{item.meta}</span>}
|
||||
{item.href && (
|
||||
<a class="font-semibold text-[var(--color-ink-900)] underline decoration-[rgba(17,36,58,0.18)] underline-offset-4" href={item.href}>
|
||||
Learn more
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
11
apps/website/src/components/content/Headline.astro
Normal file
11
apps/website/src/components/content/Headline.astro
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { class: className = '' } = Astro.props;
|
||||
---
|
||||
|
||||
<h2 class:list={['m-0 font-[var(--font-display)] text-4xl leading-[0.98] tracking-[-0.03em] text-[var(--color-ink-900)] sm:text-5xl', className]}>
|
||||
<slot />
|
||||
</h2>
|
||||
19
apps/website/src/components/content/IntegrationBadge.astro
Normal file
19
apps/website/src/components/content/IntegrationBadge.astro
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
import Badge from '@/components/primitives/Badge.astro';
|
||||
import type { IntegrationEntry } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
item: IntegrationEntry;
|
||||
}
|
||||
|
||||
const { item } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="rounded-[1.1rem] border border-[rgba(17,36,58,0.08)] bg-white/78 px-4 py-3 shadow-[var(--shadow-soft)]">
|
||||
<div class="flex items-center gap-3">
|
||||
<Badge tone="neutral">{item.category}</Badge>
|
||||
<p class="m-0 text-base font-semibold text-[var(--color-ink-900)]">{item.name}</p>
|
||||
</div>
|
||||
<p class="mt-3 max-w-72 text-sm leading-6 text-[var(--color-copy)]">{item.summary}</p>
|
||||
{item.note && <p class="mt-2 text-xs font-medium uppercase tracking-[0.14em] text-[var(--color-brand)]">{item.note}</p>}
|
||||
</div>
|
||||
11
apps/website/src/components/content/Lead.astro
Normal file
11
apps/website/src/components/content/Lead.astro
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { class: className = '' } = Astro.props;
|
||||
---
|
||||
|
||||
<p class:list={['m-0 text-base leading-8 text-[var(--color-copy)] sm:text-lg', className]}>
|
||||
<slot />
|
||||
</p>
|
||||
18
apps/website/src/components/content/Metric.astro
Normal file
18
apps/website/src/components/content/Metric.astro
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import type { MetricItem } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
item: MetricItem;
|
||||
}
|
||||
|
||||
const { item } = Astro.props;
|
||||
---
|
||||
|
||||
<Card variant="subtle">
|
||||
<p class="m-0 text-3xl font-semibold tracking-[-0.04em] text-[var(--color-ink-900)]">{item.value}</p>
|
||||
<p class="mt-2 text-sm font-semibold uppercase tracking-[0.14em] text-[var(--color-brand)]">
|
||||
{item.label}
|
||||
</p>
|
||||
<p class="mt-2 text-sm leading-6 text-[var(--color-copy)]">{item.description}</p>
|
||||
</Card>
|
||||
12
apps/website/src/components/content/PrimaryCTA.astro
Normal file
12
apps/website/src/components/content/PrimaryCTA.astro
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
import Button from '@/components/primitives/Button.astro';
|
||||
import type { CtaLink } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
cta: CtaLink;
|
||||
}
|
||||
|
||||
const { cta } = Astro.props;
|
||||
---
|
||||
|
||||
<Button href={cta.href} variant={cta.variant ?? 'primary'}>{cta.label}</Button>
|
||||
29
apps/website/src/components/content/RichText.astro
Normal file
29
apps/website/src/components/content/RichText.astro
Normal file
@ -0,0 +1,29 @@
|
||||
---
|
||||
import type { LegalSection } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
sections: LegalSection[];
|
||||
}
|
||||
|
||||
const { sections } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="space-y-6">
|
||||
{
|
||||
sections.map((section) => (
|
||||
<section class="rounded-[1.5rem] border border-[rgba(17,36,58,0.08)] bg-white/72 p-6 shadow-[var(--shadow-soft)]">
|
||||
<h2 class="m-0 text-2xl font-semibold tracking-[-0.03em] text-[var(--color-ink-900)]">
|
||||
{section.title}
|
||||
</h2>
|
||||
<div class="legal-prose mt-4">
|
||||
{section.body.map((paragraph) => <p>{paragraph}</p>)}
|
||||
{section.bullets && section.bullets.length > 0 && (
|
||||
<ul>
|
||||
{section.bullets.map((bullet) => <li>{bullet}</li>)}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
12
apps/website/src/components/content/SecondaryCTA.astro
Normal file
12
apps/website/src/components/content/SecondaryCTA.astro
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
import Button from '@/components/primitives/Button.astro';
|
||||
import type { CtaLink } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
cta: CtaLink;
|
||||
}
|
||||
|
||||
const { cta } = Astro.props;
|
||||
---
|
||||
|
||||
<Button href={cta.href} variant={cta.variant ?? 'secondary'}>{cta.label}</Button>
|
||||
16
apps/website/src/components/content/TrustPrincipleCard.astro
Normal file
16
apps/website/src/components/content/TrustPrincipleCard.astro
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import type { TrustPrincipleContent } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
item: TrustPrincipleContent;
|
||||
}
|
||||
|
||||
const { item } = Astro.props;
|
||||
---
|
||||
|
||||
<Card class="h-full">
|
||||
<h3 class="m-0 text-2xl font-semibold tracking-[-0.03em] text-[var(--color-ink-900)]">{item.title}</h3>
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">{item.description}</p>
|
||||
{item.note && <p class="mt-4 text-sm font-medium text-[var(--color-brand)]">{item.note}</p>}
|
||||
</Card>
|
||||
59
apps/website/src/components/layout/Footer.astro
Normal file
59
apps/website/src/components/layout/Footer.astro
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
import Button from '@/components/primitives/Button.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import { contactCta, footerNavigationGroups, siteMetadata } from '@/lib/site';
|
||||
|
||||
interface Props {
|
||||
currentPath: string;
|
||||
}
|
||||
|
||||
const { currentPath: _currentPath } = Astro.props;
|
||||
const currentYear = new Date().getFullYear();
|
||||
---
|
||||
|
||||
<footer class="section-divider pt-10 sm:pt-12">
|
||||
<Container wide>
|
||||
<div class="grid gap-8 rounded-[2rem] bg-[rgba(255,255,255,0.58)] p-6 shadow-[var(--shadow-soft)] lg:grid-cols-[1.3fr,1fr] lg:p-8">
|
||||
<div class="space-y-5">
|
||||
<p class="m-0 text-sm font-semibold uppercase tracking-[0.16em] text-[var(--color-brand)]">
|
||||
{siteMetadata.siteName}
|
||||
</p>
|
||||
<h2 class="m-0 max-w-xl font-[var(--font-display)] text-3xl leading-[0.98] text-[var(--color-ink-900)] sm:text-4xl">
|
||||
A calmer public surface for teams that need governance clarity before they need another dashboard.
|
||||
</h2>
|
||||
<p class="m-0 max-w-xl text-base leading-7 text-[var(--color-copy)]">
|
||||
TenantAtlas keeps product explanation, trust framing, and next-step guidance readable without hiding the product model behind hype or placeholders.
|
||||
</p>
|
||||
<Button href={contactCta.href} variant="primary" size="sm">{contactCta.label}</Button>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 sm:grid-cols-3">
|
||||
{
|
||||
footerNavigationGroups.map((group) => (
|
||||
<div>
|
||||
<p class="m-0 text-sm font-semibold uppercase tracking-[0.14em] text-[var(--color-ink-900)]">
|
||||
{group.title}
|
||||
</p>
|
||||
<ul class="mt-4 space-y-3 p-0 text-sm text-[var(--color-copy)]">
|
||||
{group.items.map((item) => (
|
||||
<li class="list-none">
|
||||
<a class="transition hover:text-[var(--color-brand)]" href={item.href}>
|
||||
{item.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 py-6 text-sm text-[var(--color-copy)] sm:flex-row sm:items-center sm:justify-between">
|
||||
<p class="m-0">© {currentYear} {siteMetadata.siteName}. Public product site v0 foundation.</p>
|
||||
<p class="m-0">
|
||||
Built as a static Astro track with no platform auth, session, or API coupling.
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
</footer>
|
||||
105
apps/website/src/components/layout/Navbar.astro
Normal file
105
apps/website/src/components/layout/Navbar.astro
Normal file
@ -0,0 +1,105 @@
|
||||
---
|
||||
import Button from '@/components/primitives/Button.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import { contactCta, isActiveNavigationPath, primaryNavigation, siteMetadata } from '@/lib/site';
|
||||
|
||||
interface Props {
|
||||
currentPath: string;
|
||||
}
|
||||
|
||||
const { currentPath } = Astro.props;
|
||||
---
|
||||
|
||||
<header class="sticky top-0 z-30 pt-4 sm:pt-6">
|
||||
<Container wide>
|
||||
<div
|
||||
class="glass-panel flex items-center justify-between gap-4 rounded-[1.75rem] border border-white/70 px-4 py-3 sm:px-5"
|
||||
>
|
||||
<a href="/" class="flex min-w-0 items-center gap-3 no-underline">
|
||||
<span
|
||||
class="inline-flex h-11 w-11 items-center justify-center rounded-full bg-[linear-gradient(135deg,var(--color-brand),#7fa6cf)] font-[var(--font-display)] text-lg text-white"
|
||||
>
|
||||
TA
|
||||
</span>
|
||||
<span class="min-w-0">
|
||||
<span class="block truncate text-sm font-semibold uppercase tracking-[0.16em] text-[var(--color-ink-900)]">
|
||||
{siteMetadata.siteName}
|
||||
</span>
|
||||
<span class="block truncate text-sm text-[var(--color-copy)]">
|
||||
{siteMetadata.siteTagline}
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<nav class="hidden items-center gap-1 lg:flex" aria-label="Primary">
|
||||
{
|
||||
primaryNavigation.map((item) => (
|
||||
<a
|
||||
href={item.href}
|
||||
class:list={[
|
||||
'rounded-full px-4 py-2 text-sm font-medium transition',
|
||||
isActiveNavigationPath(currentPath, item.href)
|
||||
? 'bg-[var(--color-brand-soft)] text-[var(--color-brand)]'
|
||||
: 'text-[var(--color-ink-800)] hover:bg-white/70',
|
||||
]}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</nav>
|
||||
|
||||
<div class="hidden lg:block">
|
||||
<Button href={contactCta.href} variant="secondary" size="sm">{contactCta.label}</Button>
|
||||
</div>
|
||||
|
||||
<details class="relative lg:hidden">
|
||||
<summary
|
||||
aria-label="Open navigation menu"
|
||||
class="flex h-11 w-11 cursor-pointer list-none items-center justify-center rounded-full border border-[color:var(--color-line)] bg-white/85 text-[var(--color-ink-900)]"
|
||||
>
|
||||
<span class="sr-only">Open navigation menu</span>
|
||||
<span class="flex flex-col gap-1">
|
||||
<span class="block h-0.5 w-4 bg-current"></span>
|
||||
<span class="block h-0.5 w-4 bg-current"></span>
|
||||
<span class="block h-0.5 w-4 bg-current"></span>
|
||||
</span>
|
||||
</summary>
|
||||
<div
|
||||
class="glass-panel absolute right-0 top-[calc(100%+0.75rem)] w-[min(18rem,88vw)] rounded-[1.5rem] border border-white/80 p-3"
|
||||
>
|
||||
<nav class="flex flex-col gap-1" aria-label="Mobile primary">
|
||||
{
|
||||
primaryNavigation.map((item) => (
|
||||
<a
|
||||
href={item.href}
|
||||
class:list={[
|
||||
'rounded-[1rem] px-4 py-3 text-sm',
|
||||
isActiveNavigationPath(currentPath, item.href)
|
||||
? 'bg-[var(--color-brand-soft)] text-[var(--color-brand)]'
|
||||
: 'text-[var(--color-ink-800)] hover:bg-white/75',
|
||||
]}
|
||||
>
|
||||
<span class="block font-semibold">{item.label}</span>
|
||||
{item.description && (
|
||||
<span class="mt-1 block text-xs text-[var(--color-copy)]">
|
||||
{item.description}
|
||||
</span>
|
||||
)}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
<div class="mt-2 rounded-[1rem] bg-[rgba(47,111,183,0.08)] p-3">
|
||||
<p class="m-0 text-sm font-semibold text-[var(--color-ink-900)]">
|
||||
{contactCta.label}
|
||||
</p>
|
||||
{contactCta.helper && (
|
||||
<p class="mt-1 text-sm text-[var(--color-copy)]">{contactCta.helper}</p>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</Container>
|
||||
</header>
|
||||
39
apps/website/src/components/layout/PageShell.astro
Normal file
39
apps/website/src/components/layout/PageShell.astro
Normal file
@ -0,0 +1,39 @@
|
||||
---
|
||||
import Footer from '@/components/layout/Footer.astro';
|
||||
import Navbar from '@/components/layout/Navbar.astro';
|
||||
import { resolveSeo } from '@/lib/seo';
|
||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||
|
||||
interface Props {
|
||||
currentPath: string;
|
||||
description?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const { currentPath, description, title } = Astro.props;
|
||||
const seo =
|
||||
title && description
|
||||
? resolveSeo({ description, path: currentPath, title })
|
||||
: undefined;
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title={title}
|
||||
description={description}
|
||||
canonicalUrl={seo?.canonicalUrl}
|
||||
openGraphTitle={seo?.ogTitle}
|
||||
openGraphDescription={seo?.ogDescription}
|
||||
robots={seo?.robots}
|
||||
>
|
||||
<div class="surface-shell min-h-screen">
|
||||
<div
|
||||
class="pointer-events-none absolute inset-x-0 top-0 -z-10 h-[30rem] bg-[radial-gradient(circle_at_top,rgba(47,111,183,0.16),transparent_50%),radial-gradient(circle_at_top_right,rgba(59,139,120,0.14),transparent_28%)]"
|
||||
>
|
||||
</div>
|
||||
<Navbar currentPath={currentPath} />
|
||||
<main id="content" class="pb-16 sm:pb-20">
|
||||
<slot />
|
||||
</main>
|
||||
<Footer currentPath={currentPath} />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
25
apps/website/src/components/primitives/Badge.astro
Normal file
25
apps/website/src/components/primitives/Badge.astro
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
tone?: 'accent' | 'neutral' | 'signal' | 'warm';
|
||||
}
|
||||
|
||||
const { class: className = '', tone = 'accent' } = Astro.props;
|
||||
|
||||
const toneClasses = {
|
||||
accent: 'bg-[var(--color-brand-soft)] text-[var(--color-brand)]',
|
||||
neutral: 'bg-white/75 text-[var(--color-ink-800)]',
|
||||
signal: 'bg-[rgba(59,139,120,0.14)] text-[var(--color-signal)]',
|
||||
warm: 'bg-[rgba(175,109,67,0.14)] text-[var(--color-warm)]',
|
||||
};
|
||||
---
|
||||
|
||||
<span
|
||||
class:list={[
|
||||
'inline-flex w-fit items-center rounded-full px-3 py-1 text-[0.72rem] font-semibold uppercase tracking-[0.18em]',
|
||||
toneClasses[tone],
|
||||
className,
|
||||
]}
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
56
apps/website/src/components/primitives/Button.astro
Normal file
56
apps/website/src/components/primitives/Button.astro
Normal file
@ -0,0 +1,56 @@
|
||||
---
|
||||
import type { ButtonVariant } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
ariaLabel?: string;
|
||||
class?: string;
|
||||
href?: string;
|
||||
rel?: string;
|
||||
size?: 'lg' | 'md' | 'sm';
|
||||
target?: '_blank' | '_self';
|
||||
type?: 'button' | 'reset' | 'submit';
|
||||
variant?: ButtonVariant;
|
||||
}
|
||||
|
||||
const {
|
||||
ariaLabel,
|
||||
class: className = '',
|
||||
href,
|
||||
rel,
|
||||
size = 'md',
|
||||
target,
|
||||
type = 'button',
|
||||
variant = 'primary',
|
||||
} = Astro.props;
|
||||
|
||||
const baseClass =
|
||||
'inline-flex items-center justify-center rounded-full border font-semibold tracking-[-0.01em] transition duration-150 focus-visible:outline-none';
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'min-h-10 px-4 text-sm',
|
||||
md: 'min-h-11 px-5 text-sm sm:text-[0.95rem]',
|
||||
lg: 'min-h-12 px-6 text-[0.95rem]',
|
||||
};
|
||||
|
||||
const variantClasses = {
|
||||
primary:
|
||||
'border-transparent bg-[var(--color-ink-900)] text-white shadow-[0_18px_38px_rgba(17,36,58,0.16)] hover:bg-[var(--color-brand)]',
|
||||
secondary:
|
||||
'border-[color:var(--color-line)] bg-white/85 text-[var(--color-ink-900)] hover:border-[var(--color-ink-900)] hover:bg-white',
|
||||
ghost: 'border-transparent bg-transparent text-[var(--color-ink-800)] hover:bg-white/70',
|
||||
};
|
||||
|
||||
const classes = [baseClass, sizeClasses[size], variantClasses[variant], className];
|
||||
---
|
||||
|
||||
{
|
||||
href ? (
|
||||
<a href={href} target={target} rel={rel} aria-label={ariaLabel} class:list={classes}>
|
||||
<slot />
|
||||
</a>
|
||||
) : (
|
||||
<button type={type} aria-label={ariaLabel} class:list={classes}>
|
||||
<slot />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
23
apps/website/src/components/primitives/Card.astro
Normal file
23
apps/website/src/components/primitives/Card.astro
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
interface Props {
|
||||
as?: keyof HTMLElementTagNameMap;
|
||||
class?: string;
|
||||
variant?: 'accent' | 'default' | 'subtle';
|
||||
}
|
||||
|
||||
const { as = 'article', class: className = '', variant = 'default' } = Astro.props;
|
||||
|
||||
const variantClasses = {
|
||||
default: 'glass-panel border border-[color:var(--color-line)] bg-[var(--color-panel)]',
|
||||
accent:
|
||||
'border border-[rgba(47,111,183,0.18)] bg-[linear-gradient(180deg,rgba(255,255,255,0.96),rgba(238,245,252,0.94))] shadow-[var(--shadow-soft)]',
|
||||
subtle:
|
||||
'border border-[rgba(17,36,58,0.08)] bg-[linear-gradient(180deg,rgba(255,255,255,0.78),rgba(255,255,255,0.56))]',
|
||||
};
|
||||
|
||||
const Tag = as;
|
||||
---
|
||||
|
||||
<Tag class:list={['rounded-[1.65rem] p-6 sm:p-7', variantClasses[variant], className]}>
|
||||
<slot />
|
||||
</Tag>
|
||||
13
apps/website/src/components/primitives/Cluster.astro
Normal file
13
apps/website/src/components/primitives/Cluster.astro
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
interface Props {
|
||||
as?: keyof HTMLElementTagNameMap;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { as = 'div', class: className = '' } = Astro.props;
|
||||
const Tag = as;
|
||||
---
|
||||
|
||||
<Tag class:list={['flex flex-wrap items-center gap-3 sm:gap-4', className]}>
|
||||
<slot />
|
||||
</Tag>
|
||||
14
apps/website/src/components/primitives/Container.astro
Normal file
14
apps/website/src/components/primitives/Container.astro
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
interface Props {
|
||||
as?: keyof HTMLElementTagNameMap;
|
||||
class?: string;
|
||||
wide?: boolean;
|
||||
}
|
||||
|
||||
const { as = 'div', class: className = '', wide = false } = Astro.props;
|
||||
const Tag = as;
|
||||
---
|
||||
|
||||
<Tag class:list={['mx-auto w-full px-5 sm:px-6 lg:px-8', wide ? 'max-w-[80rem]' : 'max-w-6xl', className]}>
|
||||
<slot />
|
||||
</Tag>
|
||||
18
apps/website/src/components/primitives/Grid.astro
Normal file
18
apps/website/src/components/primitives/Grid.astro
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
cols?: '2' | '3' | '4';
|
||||
}
|
||||
|
||||
const { class: className = '', cols = '3' } = Astro.props;
|
||||
|
||||
const colClasses = {
|
||||
'2': 'grid-cols-1 md:grid-cols-2',
|
||||
'3': 'grid-cols-1 md:grid-cols-2 xl:grid-cols-3',
|
||||
'4': 'grid-cols-1 md:grid-cols-2 xl:grid-cols-4',
|
||||
};
|
||||
---
|
||||
|
||||
<div class:list={['grid gap-5 lg:gap-6', colClasses[cols], className]}>
|
||||
<slot />
|
||||
</div>
|
||||
32
apps/website/src/components/primitives/Input.astro
Normal file
32
apps/website/src/components/primitives/Input.astro
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
name?: string;
|
||||
placeholder?: string;
|
||||
readonly?: boolean;
|
||||
type?: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
class: className = '',
|
||||
name,
|
||||
placeholder,
|
||||
readonly = false,
|
||||
type = 'text',
|
||||
value,
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<input
|
||||
type={type}
|
||||
name={name}
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
readonly={readonly}
|
||||
class:list={[
|
||||
'min-h-12 w-full rounded-[1.1rem] border border-[color:var(--color-line)] bg-white/90 px-4 text-[0.97rem] text-[var(--color-ink-900)] shadow-[var(--shadow-soft)] placeholder:text-[var(--color-copy)]/70',
|
||||
readonly ? 'cursor-default' : '',
|
||||
className,
|
||||
]}
|
||||
/>
|
||||
22
apps/website/src/components/primitives/Section.astro
Normal file
22
apps/website/src/components/primitives/Section.astro
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
interface Props {
|
||||
as?: keyof HTMLElementTagNameMap;
|
||||
class?: string;
|
||||
id?: string;
|
||||
muted?: boolean;
|
||||
}
|
||||
|
||||
const { as = 'section', class: className = '', id, muted = false } = Astro.props;
|
||||
const Tag = as;
|
||||
---
|
||||
|
||||
<Tag
|
||||
id={id}
|
||||
class:list={[
|
||||
'py-12 sm:py-16 lg:py-20',
|
||||
muted ? 'bg-white/45' : '',
|
||||
className,
|
||||
]}
|
||||
>
|
||||
<slot />
|
||||
</Tag>
|
||||
27
apps/website/src/components/primitives/SectionHeader.astro
Normal file
27
apps/website/src/components/primitives/SectionHeader.astro
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
import Eyebrow from '@/components/content/Eyebrow.astro';
|
||||
import Headline from '@/components/content/Headline.astro';
|
||||
import Lead from '@/components/content/Lead.astro';
|
||||
|
||||
interface Props {
|
||||
align?: 'center' | 'left';
|
||||
class?: string;
|
||||
description?: string;
|
||||
eyebrow?: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const {
|
||||
align = 'left',
|
||||
class: className = '',
|
||||
description,
|
||||
eyebrow,
|
||||
title,
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<div class:list={['max-w-3xl', align === 'center' ? 'mx-auto text-center' : '', className]}>
|
||||
{eyebrow && <Eyebrow>{eyebrow}</Eyebrow>}
|
||||
<Headline>{title}</Headline>
|
||||
{description && <Lead class="mt-4">{description}</Lead>}
|
||||
</div>
|
||||
20
apps/website/src/components/primitives/Stack.astro
Normal file
20
apps/website/src/components/primitives/Stack.astro
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
interface Props {
|
||||
as?: keyof HTMLElementTagNameMap;
|
||||
class?: string;
|
||||
gap?: 'lg' | 'md' | 'sm' | 'xl';
|
||||
}
|
||||
|
||||
const { as = 'div', class: className = '', gap = 'md' } = Astro.props;
|
||||
const gapClasses = {
|
||||
sm: 'flex flex-col gap-3',
|
||||
md: 'flex flex-col gap-5',
|
||||
lg: 'flex flex-col gap-7',
|
||||
xl: 'flex flex-col gap-10',
|
||||
};
|
||||
const Tag = as;
|
||||
---
|
||||
|
||||
<Tag class:list={[gapClasses[gap], className]}>
|
||||
<slot />
|
||||
</Tag>
|
||||
31
apps/website/src/components/primitives/Textarea.astro
Normal file
31
apps/website/src/components/primitives/Textarea.astro
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
name?: string;
|
||||
placeholder?: string;
|
||||
readonly?: boolean;
|
||||
rows?: number;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
class: className = '',
|
||||
name,
|
||||
placeholder,
|
||||
readonly = false,
|
||||
rows = 5,
|
||||
value,
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<textarea
|
||||
name={name}
|
||||
rows={rows}
|
||||
placeholder={placeholder}
|
||||
readonly={readonly}
|
||||
class:list={[
|
||||
'min-h-32 w-full rounded-[1.1rem] border border-[color:var(--color-line)] bg-white/90 px-4 py-3 text-[0.97rem] text-[var(--color-ink-900)] shadow-[var(--shadow-soft)] placeholder:text-[var(--color-copy)]/70',
|
||||
readonly ? 'cursor-default' : '',
|
||||
className,
|
||||
]}
|
||||
>{value}</textarea>
|
||||
33
apps/website/src/components/sections/CTASection.astro
Normal file
33
apps/website/src/components/sections/CTASection.astro
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
import SecondaryCTA from '@/components/content/SecondaryCTA.astro';
|
||||
import PrimaryCTA from '@/components/content/PrimaryCTA.astro';
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import type { CtaLink } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
description: string;
|
||||
eyebrow?: string;
|
||||
primary: CtaLink;
|
||||
secondary?: CtaLink;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { description, eyebrow, primary, secondary, title } = Astro.props;
|
||||
---
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<Card variant="accent" class="overflow-hidden">
|
||||
<div class="grid gap-6 lg:grid-cols-[1.35fr,0.85fr] lg:items-end">
|
||||
<SectionHeader eyebrow={eyebrow} title={title} description={description} />
|
||||
<div class="flex flex-col gap-3 sm:flex-row lg:justify-end">
|
||||
<PrimaryCTA cta={primary} />
|
||||
{secondary && <SecondaryCTA cta={secondary} />}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Container>
|
||||
</Section>
|
||||
28
apps/website/src/components/sections/FeatureGrid.astro
Normal file
28
apps/website/src/components/sections/FeatureGrid.astro
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
import FeatureItem from '@/components/content/FeatureItem.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import type { FeatureItemContent } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
description?: string;
|
||||
eyebrow?: string;
|
||||
items: FeatureItemContent[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { description, eyebrow, items, title } = Astro.props;
|
||||
---
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="space-y-8">
|
||||
<SectionHeader eyebrow={eyebrow} title={title} description={description} />
|
||||
<Grid cols="3">
|
||||
{items.map((item) => <FeatureItem item={item} />)}
|
||||
</Grid>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
44
apps/website/src/components/sections/LogoStrip.astro
Normal file
44
apps/website/src/components/sections/LogoStrip.astro
Normal file
@ -0,0 +1,44 @@
|
||||
---
|
||||
import IntegrationBadge from '@/components/content/IntegrationBadge.astro';
|
||||
import Badge from '@/components/primitives/Badge.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import type { IntegrationEntry, LogoStripItem } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
eyebrow?: string;
|
||||
items: (IntegrationEntry | LogoStripItem)[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { eyebrow, items, title } = Astro.props;
|
||||
---
|
||||
|
||||
<Section class="pt-8 sm:pt-10">
|
||||
<Container wide>
|
||||
<div class="rounded-[1.8rem] border border-[rgba(17,36,58,0.08)] bg-white/55 px-5 py-6 shadow-[var(--shadow-soft)]">
|
||||
<div class="flex flex-col gap-5 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div class="space-y-3">
|
||||
{eyebrow && <Badge tone="signal">{eyebrow}</Badge>}
|
||||
<h2 class="m-0 max-w-2xl font-[var(--font-display)] text-3xl leading-tight text-[var(--color-ink-900)]">
|
||||
{title}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
{
|
||||
items.map((item) => (
|
||||
<IntegrationBadge
|
||||
item={{
|
||||
category: 'category' in item ? item.category : 'Ecosystem',
|
||||
name: item.label ?? item.name,
|
||||
note: item.note,
|
||||
summary: 'summary' in item ? item.summary : `${item.label} aligns with the launch story.`,
|
||||
}}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
96
apps/website/src/components/sections/PageHero.astro
Normal file
96
apps/website/src/components/sections/PageHero.astro
Normal file
@ -0,0 +1,96 @@
|
||||
---
|
||||
import Badge from '@/components/primitives/Badge.astro';
|
||||
import Button from '@/components/primitives/Button.astro';
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import Cluster from '@/components/primitives/Cluster.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import type { HeroContent, MetricItem } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
calloutDescription?: string;
|
||||
calloutTitle?: string;
|
||||
hero: HeroContent;
|
||||
metrics?: MetricItem[];
|
||||
}
|
||||
|
||||
const { calloutDescription, calloutTitle, hero, metrics = [] } = Astro.props;
|
||||
---
|
||||
|
||||
<section class="pt-8 sm:pt-10 lg:pt-14">
|
||||
<Container wide>
|
||||
<div class="grid gap-6 lg:grid-cols-[1.35fr,0.85fr]">
|
||||
<Card class="motion-rise overflow-hidden">
|
||||
<div class="space-y-6">
|
||||
<Badge>{hero.eyebrow}</Badge>
|
||||
<div class="space-y-4">
|
||||
<h1 class="max-w-4xl font-[var(--font-display)] text-5xl leading-[0.93] tracking-[-0.04em] text-[var(--color-ink-900)] sm:text-6xl lg:text-7xl">
|
||||
{hero.title}
|
||||
</h1>
|
||||
<p class="max-w-3xl text-lg leading-8 text-[var(--color-copy)] sm:text-xl">
|
||||
{hero.description}
|
||||
</p>
|
||||
</div>
|
||||
{(hero.primaryCta || hero.secondaryCta) && (
|
||||
<Cluster>
|
||||
<Button href={hero.primaryCta.href} variant={hero.primaryCta.variant ?? 'primary'}>
|
||||
{hero.primaryCta.label}
|
||||
</Button>
|
||||
{hero.secondaryCta && (
|
||||
<Button href={hero.secondaryCta.href} variant={hero.secondaryCta.variant ?? 'secondary'}>
|
||||
{hero.secondaryCta.label}
|
||||
</Button>
|
||||
)}
|
||||
</Cluster>
|
||||
)}
|
||||
{hero.highlights && hero.highlights.length > 0 && (
|
||||
<ul class="grid gap-3 p-0 sm:grid-cols-3">
|
||||
{
|
||||
hero.highlights.map((highlight) => (
|
||||
<li class="list-none rounded-[1.1rem] border border-[color:var(--color-line)] bg-white/70 px-4 py-3 text-sm font-medium text-[var(--color-ink-800)]">
|
||||
{highlight}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div class="grid gap-5">
|
||||
{(calloutTitle || calloutDescription) && (
|
||||
<Card variant="accent" class="motion-rise">
|
||||
<p class="m-0 text-sm font-semibold uppercase tracking-[0.15em] text-[var(--color-brand)]">
|
||||
Trust-first launch surface
|
||||
</p>
|
||||
{calloutTitle && (
|
||||
<h2 class="mt-4 font-[var(--font-display)] text-3xl leading-tight text-[var(--color-ink-900)]">
|
||||
{calloutTitle}
|
||||
</h2>
|
||||
)}
|
||||
{calloutDescription && (
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">{calloutDescription}</p>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{metrics.length > 0 && (
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-1">
|
||||
{
|
||||
metrics.map((metric) => (
|
||||
<Card variant="subtle">
|
||||
<p class="m-0 text-3xl font-semibold tracking-[-0.04em] text-[var(--color-ink-900)]">
|
||||
{metric.value}
|
||||
</p>
|
||||
<p class="mt-2 text-sm font-semibold uppercase tracking-[0.14em] text-[var(--color-brand)]">
|
||||
{metric.label}
|
||||
</p>
|
||||
<p class="mt-2 text-sm leading-6 text-[var(--color-copy)]">{metric.description}</p>
|
||||
</Card>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
28
apps/website/src/components/sections/TrustGrid.astro
Normal file
28
apps/website/src/components/sections/TrustGrid.astro
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
import TrustPrincipleCard from '@/components/content/TrustPrincipleCard.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import type { TrustPrincipleContent } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
description?: string;
|
||||
eyebrow?: string;
|
||||
items: TrustPrincipleContent[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { description, eyebrow, items, title } = Astro.props;
|
||||
---
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="space-y-8">
|
||||
<SectionHeader eyebrow={eyebrow} title={title} description={description} />
|
||||
<Grid cols="3">
|
||||
{items.map((item) => <TrustPrincipleCard item={item} />)}
|
||||
</Grid>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
24
apps/website/src/content.config.ts
Normal file
24
apps/website/src/content.config.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { glob } from 'astro/loaders';
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
|
||||
const futureContentSchema = z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
publishedAt: z.coerce.date().optional(),
|
||||
draft: z.boolean().default(false),
|
||||
});
|
||||
|
||||
export const collections = {
|
||||
articles: defineCollection({
|
||||
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/articles' }),
|
||||
schema: futureContentSchema,
|
||||
}),
|
||||
changelog: defineCollection({
|
||||
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/changelog' }),
|
||||
schema: futureContentSchema,
|
||||
}),
|
||||
resources: defineCollection({
|
||||
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/resources' }),
|
||||
schema: futureContentSchema,
|
||||
}),
|
||||
};
|
||||
65
apps/website/src/content/pages/contact.ts
Normal file
65
apps/website/src/content/pages/contact.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
||||
|
||||
export const contactSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Contact',
|
||||
description:
|
||||
'TenantAtlas uses a qualified working-session path instead of a generic demo pitch so serious buyers can frame the right conversation early.',
|
||||
path: '/contact',
|
||||
};
|
||||
|
||||
export const contactHero: HeroContent = {
|
||||
eyebrow: 'Contact / Demo',
|
||||
title: 'Start a qualified working session instead of a generic demo request.',
|
||||
description:
|
||||
'The contact path should help serious buyers explain who they are, what governance questions they are trying to solve, and what kind of follow-up would actually be useful.',
|
||||
primaryCta: {
|
||||
href: 'mailto:hello@tenantatlas.example?subject=TenantAtlas%20working%20session',
|
||||
label: 'Email the TenantAtlas team',
|
||||
variant: 'primary',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/legal',
|
||||
label: 'Read the legal surface',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'Use the page to qualify the conversation, not to force a form funnel.',
|
||||
'Set expectations for what the session covers and what happens next.',
|
||||
'Keep privacy and terms visible before anyone shares evaluation details.',
|
||||
],
|
||||
};
|
||||
|
||||
export const contactTopics = [
|
||||
'Evaluation of backup, restore, or version-governance workflows for Intune and Microsoft tenant operations.',
|
||||
'Questions about MSP fit, customer-facing evidence, or multi-tenant operational discipline.',
|
||||
'Internal enterprise review of change history, drift visibility, evidence collection, or restore posture.',
|
||||
];
|
||||
|
||||
export const contactPrompts = [
|
||||
{
|
||||
title: 'Good first conversation',
|
||||
description:
|
||||
'Explain the current operating model, where version history breaks down today, and which changes feel hardest to review or restore safely.',
|
||||
},
|
||||
{
|
||||
title: 'Useful context to share',
|
||||
description:
|
||||
'Team shape, tenant count, policy complexity, change frequency, and whether the first concern is restore safety, auditability, or review evidence.',
|
||||
},
|
||||
];
|
||||
|
||||
export const contactPreview = {
|
||||
message:
|
||||
'We operate Microsoft tenant governance across multiple environments and want to understand how TenantAtlas approaches version history, safer restore flows, drift visibility, and review evidence.',
|
||||
topic: 'Environment and operating model summary',
|
||||
};
|
||||
|
||||
export const contactLegalSections: LegalSection[] = [
|
||||
{
|
||||
title: 'Before you reach out',
|
||||
body: [
|
||||
'Use the legal links below before sharing evaluation details so the contact path stays trustworthy and unsurprising.',
|
||||
'The legal hub, privacy page, and public website terms remain reachable from the contact flow and the global footer.',
|
||||
],
|
||||
},
|
||||
];
|
||||
145
apps/website/src/content/pages/home.ts
Normal file
145
apps/website/src/content/pages/home.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import type {
|
||||
CalloutContent,
|
||||
FeatureItemContent,
|
||||
HeroContent,
|
||||
IntegrationEntry,
|
||||
MetricItem,
|
||||
PageSeo,
|
||||
} from '@/types/site';
|
||||
|
||||
export const homeSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Governance of record for Microsoft tenant operations',
|
||||
description:
|
||||
'Trust-first public framing for a Microsoft tenant governance product that connects backup, restore, version history, drift, findings, evidence, and reviews.',
|
||||
path: '/',
|
||||
};
|
||||
|
||||
export const homeHero: HeroContent = {
|
||||
eyebrow: 'Public website v0',
|
||||
title: 'TenantAtlas is the trust-first public site for Microsoft tenant change history, drift visibility, and safer operations.',
|
||||
description:
|
||||
'TenantAtlas gives MSP and enterprise teams one clear operating model for understanding what changed, what drifted, what needs review, and what can be restored without turning governance into a loose collection of disconnected tools.',
|
||||
primaryCta: {
|
||||
href: '/product',
|
||||
label: 'See the product model',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/security-trust',
|
||||
label: 'Review the trust posture',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'Static, readable, and separate from app runtime concerns.',
|
||||
'Built for trust conversations before a demo ever starts.',
|
||||
'Designed to scale into docs, changelog, and deeper resources later.',
|
||||
],
|
||||
};
|
||||
|
||||
export const homeMetrics: MetricItem[] = [
|
||||
{
|
||||
value: '1',
|
||||
label: 'Connected model',
|
||||
description: 'Inventory, snapshots, review evidence, and restore posture stay in one narrative.',
|
||||
},
|
||||
{
|
||||
value: '7+',
|
||||
label: 'Core public surfaces',
|
||||
description: 'Visitors can move from explanation to trust and contact without dead ends.',
|
||||
},
|
||||
{
|
||||
value: '0',
|
||||
label: 'Runtime coupling',
|
||||
description: 'The website stays independent from platform auth, session, and API behavior.',
|
||||
},
|
||||
];
|
||||
|
||||
export const homePillars: FeatureItemContent[] = [
|
||||
{
|
||||
eyebrow: 'Inventory',
|
||||
title: 'Normalize what the tenant really looks like right now.',
|
||||
description:
|
||||
'Start with the observed state so teams can inspect the current configuration baseline before they talk about restore or enforcement.',
|
||||
meta: 'Last observed truth',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Snapshots',
|
||||
title: 'Keep immutable history instead of vague memory.',
|
||||
description:
|
||||
'Version history stays queryable by tenant, operator, and moment in time so teams can explain what changed and why.',
|
||||
meta: 'Reproducible versions',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Drift & findings',
|
||||
title: 'Surface drift, exceptions, and review needs in the same language.',
|
||||
description:
|
||||
'Operational questions move from “what broke?” to “what changed, what matters, and what review is due?”',
|
||||
meta: 'Review-oriented visibility',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Restore',
|
||||
title: 'Treat rollback and restore as governed actions, not panic buttons.',
|
||||
description:
|
||||
'Preview, validation, and operator confirmation stay central so risky changes are reversible without becoming casual.',
|
||||
meta: 'Safer execution',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Evidence',
|
||||
title: 'Connect reviews, findings, and evidence without a second reporting layer.',
|
||||
description:
|
||||
'Teams can show why a configuration is acceptable, where exceptions exist, and how review decisions stay attributable.',
|
||||
meta: 'Audit-ready context',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Operations',
|
||||
title: 'Keep velocity without hiding risk.',
|
||||
description:
|
||||
'The product is built for admins who need speed and auditability at the same time, not for dashboards that only summarize after the fact.',
|
||||
meta: 'Operator-first workflows',
|
||||
},
|
||||
];
|
||||
|
||||
export const homeProofBlocks: CalloutContent[] = [
|
||||
{
|
||||
eyebrow: 'Positioning',
|
||||
title: 'Governance of record for Microsoft tenant operations.',
|
||||
description:
|
||||
'The site makes the category legible up front: not just backup, not just reporting, and not a second admin portal trying to mirror every Microsoft screen.',
|
||||
tone: 'accent',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Why it matters now',
|
||||
title: 'Microsoft tenant change volume keeps climbing while operator certainty keeps shrinking.',
|
||||
description:
|
||||
'When policy history, restore posture, findings, and evidence live in separate conversations, teams lose time exactly when they need clarity.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Public promise',
|
||||
title: 'No inflated compliance or automation claims.',
|
||||
description:
|
||||
'The public story stays grounded in what the product can honestly support at launch: version truth, safer restore flows, drift visibility, and review support.',
|
||||
tone: 'subtle',
|
||||
},
|
||||
];
|
||||
|
||||
export const homeEcosystem: IntegrationEntry[] = [
|
||||
{
|
||||
category: 'Microsoft',
|
||||
name: 'Microsoft Graph',
|
||||
summary: 'Graph-backed inventory and restore direction without pretending the website depends on live tenant access.',
|
||||
},
|
||||
{
|
||||
category: 'Identity',
|
||||
name: 'Entra ID',
|
||||
summary: 'Identity and access context remain part of the governance narrative where they matter to change control.',
|
||||
},
|
||||
{
|
||||
category: 'Endpoint',
|
||||
name: 'Intune',
|
||||
summary: 'Configuration state, backup, and restore posture stay central to the public product story.',
|
||||
},
|
||||
{
|
||||
category: 'Review',
|
||||
name: 'Evidence workflows',
|
||||
summary: 'Review packs, exceptions, and evidence stay connected to operational reality instead of becoming detached reporting artifacts.',
|
||||
},
|
||||
];
|
||||
86
apps/website/src/content/pages/integrations.ts
Normal file
86
apps/website/src/content/pages/integrations.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import type {
|
||||
FeatureItemContent,
|
||||
HeroContent,
|
||||
IntegrationEntry,
|
||||
PageSeo,
|
||||
} from '@/types/site';
|
||||
|
||||
export const integrationsSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Integrations',
|
||||
description:
|
||||
'TenantAtlas describes the Microsoft-centric ecosystem it fits today without turning the page into a public wishlist.',
|
||||
path: '/integrations',
|
||||
};
|
||||
|
||||
export const integrationsHero: HeroContent = {
|
||||
eyebrow: 'Ecosystem fit',
|
||||
title: 'Stay clear about the ecosystem fit without turning the page into a wishlist.',
|
||||
description:
|
||||
'This page should show the real systems TenantAtlas is built around, the workflows it expects to support, and the deliberate boundaries where speculative integration language would create more noise than trust.',
|
||||
primaryCta: {
|
||||
href: '/contact',
|
||||
label: 'Plan the working session',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/product',
|
||||
label: 'Revisit the product model',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'Microsoft-first and governance-led.',
|
||||
'Real direction only, no catalog-padding.',
|
||||
'Explains fit without implying runtime coupling to the public site.',
|
||||
],
|
||||
};
|
||||
|
||||
export const integrationEntries: IntegrationEntry[] = [
|
||||
{
|
||||
category: 'Microsoft core',
|
||||
name: 'Microsoft Graph',
|
||||
summary:
|
||||
'Graph remains the primary contract path for inventory, history, and restore-oriented product behavior in the platform.',
|
||||
note: 'Core product contract',
|
||||
},
|
||||
{
|
||||
category: 'Identity',
|
||||
name: 'Entra ID',
|
||||
summary:
|
||||
'Identity context matters where tenant change, privileged access, or governance reviews intersect with Microsoft tenant administration.',
|
||||
note: 'Operational context',
|
||||
},
|
||||
{
|
||||
category: 'Endpoint',
|
||||
name: 'Intune',
|
||||
summary:
|
||||
'Intune configuration state is central to the current product story, especially for version history, backup, restore, and drift visibility.',
|
||||
note: 'Current release truth',
|
||||
},
|
||||
{
|
||||
category: 'Evidence',
|
||||
name: 'Review & evidence workflows',
|
||||
summary:
|
||||
'Exports, review packs, and evidence-linked conversations should remain grounded in the actual tenant object and its change history.',
|
||||
note: 'Governance workflow fit',
|
||||
},
|
||||
];
|
||||
|
||||
export const integrationRules: FeatureItemContent[] = [
|
||||
{
|
||||
eyebrow: 'Direction',
|
||||
title: 'Integrations should reinforce the governance model.',
|
||||
description:
|
||||
'The page is not a marketplace list. It should show which systems matter because they change the product workflow, evidence story, or operator context.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Boundaries',
|
||||
title: 'Do not imply shared public-site runtime dependencies.',
|
||||
description:
|
||||
'The website stays static and independent even while the product story references Graph, Intune, and Microsoft tenant governance flows.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Credibility',
|
||||
title: 'Speculative wishlist entries reduce trust instead of creating momentum.',
|
||||
description:
|
||||
'The public integrations page should stay shorter and sharper than a catalog full of future ideas that are not relevant to launch truth.',
|
||||
},
|
||||
];
|
||||
44
apps/website/src/content/pages/legal.ts
Normal file
44
apps/website/src/content/pages/legal.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
||||
|
||||
export const legalSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Legal',
|
||||
description:
|
||||
'The TenantAtlas legal surface keeps privacy, website terms, and public legal-notice routing accessible before or during buyer conversations.',
|
||||
path: '/legal',
|
||||
};
|
||||
|
||||
export const legalHero: HeroContent = {
|
||||
eyebrow: 'Legal surface',
|
||||
title: 'Legal access should stay one click away from the contact path.',
|
||||
description:
|
||||
'The legal hub keeps privacy, website terms, and public legal-notice information discoverable from the footer and the conversion flow so visitors do not have to guess where those basics live.',
|
||||
primaryCta: {
|
||||
href: '/privacy',
|
||||
label: 'Privacy',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/terms',
|
||||
label: 'Terms',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'Public legal basics stay reachable before a visitor shares evaluation context.',
|
||||
'The site separates website disclosures from future product-commercial paperwork.',
|
||||
'Jurisdiction-specific notice has a dedicated home in the legal surface.',
|
||||
],
|
||||
};
|
||||
|
||||
export const legalNoticeSections: LegalSection[] = [
|
||||
{
|
||||
title: 'Public legal notice',
|
||||
body: [
|
||||
'This v0 legal surface reserves the public location for operating-entity, registration, address, and jurisdiction-specific disclosure details that must be published before a broad public launch.',
|
||||
'During controlled evaluation, legal and privacy inquiries can be routed through the public contact path while those final publisher details are being finalized.',
|
||||
],
|
||||
bullets: [
|
||||
'Operating entity and jurisdictional disclosure fields belong in this legal hub before launch.',
|
||||
'Privacy and website terms stay published as standalone routes now.',
|
||||
'The legal hub remains the single public path for future launch-required disclosures.',
|
||||
],
|
||||
},
|
||||
];
|
||||
60
apps/website/src/content/pages/privacy.ts
Normal file
60
apps/website/src/content/pages/privacy.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
||||
|
||||
export const privacySeo: PageSeo = {
|
||||
title: 'TenantAtlas | Privacy',
|
||||
description:
|
||||
'Public-site privacy overview for TenantAtlas inquiries, including how contact details and evaluation context are handled on the public website.',
|
||||
path: '/privacy',
|
||||
};
|
||||
|
||||
export const privacyHero: HeroContent = {
|
||||
eyebrow: 'Privacy',
|
||||
title: 'Public-site privacy overview for TenantAtlas inquiries.',
|
||||
description:
|
||||
'This page explains the privacy expectations for the public website and the contact path, rather than promising a full product-tenant data processing agreement from a static marketing surface.',
|
||||
primaryCta: {
|
||||
href: '/contact',
|
||||
label: 'Return to contact',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/terms',
|
||||
label: 'Review website terms',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'The public site should only request information that supports a useful follow-up.',
|
||||
'Contact details and evaluation context should be handled carefully and minimally.',
|
||||
'Future product-processing details belong in product/legal agreements, not hidden marketing copy.',
|
||||
],
|
||||
};
|
||||
|
||||
export const privacySections: LegalSection[] = [
|
||||
{
|
||||
title: 'Scope',
|
||||
body: [
|
||||
'This privacy overview applies to the public TenantAtlas website and to information a visitor intentionally shares through the public contact path.',
|
||||
'It does not describe tenant data processing inside the product itself, which belongs in product-specific legal and contractual materials.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Information you choose to send',
|
||||
body: [
|
||||
'If you contact the team, the information you provide may include your name, company, role, email address, and the evaluation or governance questions you want to discuss.',
|
||||
'The site should not ask for unnecessary secrets, production credentials, or tenant data through the public contact path.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Use and retention',
|
||||
body: [
|
||||
'Information shared through the public contact path is used to understand the inquiry, respond to the request, and coordinate a relevant follow-up conversation.',
|
||||
'Public-site inquiry information should be retained only for as long as needed to manage the evaluation discussion and related follow-up.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Questions and updates',
|
||||
body: [
|
||||
'Privacy questions can be routed through the public contact path until the final launch legal notice publishes the full operating-entity details for privacy correspondence.',
|
||||
'If the public-site data handling model changes materially, this page should be updated before or at the same time as the change.',
|
||||
],
|
||||
},
|
||||
];
|
||||
110
apps/website/src/content/pages/product.ts
Normal file
110
apps/website/src/content/pages/product.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import type {
|
||||
CalloutContent,
|
||||
FeatureItemContent,
|
||||
HeroContent,
|
||||
MetricItem,
|
||||
PageSeo,
|
||||
} from '@/types/site';
|
||||
|
||||
export const productSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Product',
|
||||
description:
|
||||
'TenantAtlas connects inventory, snapshots, restore safety, drift visibility, findings, exceptions, and evidence into one governance model.',
|
||||
path: '/product',
|
||||
};
|
||||
|
||||
export const productHero: HeroContent = {
|
||||
eyebrow: 'Product model',
|
||||
title: 'One operating model for change history, drift visibility, and review readiness.',
|
||||
description:
|
||||
'TenantAtlas treats Microsoft tenant governance as one connected system: observe the current state, preserve immutable history, detect meaningful change, and support reviews or restores with the context operators actually need.',
|
||||
primaryCta: {
|
||||
href: '/solutions',
|
||||
label: 'See audience fit',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/contact',
|
||||
label: 'Talk through your current operating model',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'Inventory first, snapshots second.',
|
||||
'Restore flows stay previewable and attributable.',
|
||||
'Evidence and review posture stay connected to real change history.',
|
||||
],
|
||||
};
|
||||
|
||||
export const productMetrics: MetricItem[] = [
|
||||
{
|
||||
value: '4',
|
||||
label: 'Operator questions',
|
||||
description: 'What changed? Why does it matter? What can be restored? What needs review now?',
|
||||
},
|
||||
{
|
||||
value: '100%',
|
||||
label: 'Queryable versions',
|
||||
description: 'Version semantics stay tied to who changed what, when, and in which tenant context.',
|
||||
},
|
||||
];
|
||||
|
||||
export const productModelBlocks: FeatureItemContent[] = [
|
||||
{
|
||||
eyebrow: 'Connected governance model',
|
||||
title: 'Inventory creates the starting point for every other decision.',
|
||||
description:
|
||||
'The product begins with the last observed tenant state so teams can compare real configuration truth instead of relying on partial memory or exported spreadsheets.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Connected governance model',
|
||||
title: 'Snapshots add immutable history without replacing current truth.',
|
||||
description:
|
||||
'Backups and versions are explicit artifacts. They preserve what was seen at a point in time while keeping the present-tense inventory readable.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Connected governance model',
|
||||
title: 'Restore is handled as a governed operation, not as a blind push.',
|
||||
description:
|
||||
'Preview, validation, selective scope, and confirmation reduce the risk of turning a recovery step into a new incident.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Drift visibility',
|
||||
title: 'Differences become reviewable signals instead of noisy raw deltas.',
|
||||
description:
|
||||
'Human-readable summaries and structured differences help operators and reviewers decide what changed and what needs action.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Exceptions & evidence',
|
||||
title: 'Findings, exceptions, and evidence stay anchored to operational truth.',
|
||||
description:
|
||||
'Governance discussions stay attached to the real object, version, and review context instead of drifting into separate manual trackers.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Operator safety',
|
||||
title: 'Auditability is part of the product shape, not a later add-on.',
|
||||
description:
|
||||
'The product is built so teams can explain actions afterward, not just execute them quickly in the moment.',
|
||||
},
|
||||
];
|
||||
|
||||
export const productNarrative: CalloutContent[] = [
|
||||
{
|
||||
eyebrow: 'Why it is not a feature list',
|
||||
title: 'The point is not “backup plus reporting plus restore.”',
|
||||
description:
|
||||
'The point is to reduce operator uncertainty by keeping those capabilities connected through the same source material and the same decision flow.',
|
||||
tone: 'accent',
|
||||
},
|
||||
{
|
||||
eyebrow: 'What teams get',
|
||||
title: 'A calmer path from observation to action.',
|
||||
description:
|
||||
'Teams can move from understanding the current tenant state to comparing history, planning remediation, or reviewing restore options without leaving the product model behind.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'What teams avoid',
|
||||
title: 'No generic dashboard theater.',
|
||||
description:
|
||||
'The product story avoids pretending that another alerting page or compliance badge alone solves governance discipline.',
|
||||
tone: 'subtle',
|
||||
},
|
||||
];
|
||||
78
apps/website/src/content/pages/security-trust.ts
Normal file
78
apps/website/src/content/pages/security-trust.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import type {
|
||||
CalloutContent,
|
||||
HeroContent,
|
||||
PageSeo,
|
||||
TrustPrincipleContent,
|
||||
} from '@/types/site';
|
||||
|
||||
export const securityTrustSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Security & Trust',
|
||||
description:
|
||||
'TenantAtlas frames trust through substantiated product posture, safer restore discipline, and operational clarity rather than inflated guarantees.',
|
||||
path: '/security-trust',
|
||||
};
|
||||
|
||||
export const securityTrustHero: HeroContent = {
|
||||
eyebrow: 'Security & Trust',
|
||||
title: 'Explain the trust posture in the language of operational controls, not marketing claims.',
|
||||
description:
|
||||
'The public trust page should set realistic expectations: what the product helps teams observe, preserve, review, and restore, and where launch claims intentionally stay narrow until they can be substantiated further.',
|
||||
primaryCta: {
|
||||
href: '/legal',
|
||||
label: 'Read the legal surface',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/contact',
|
||||
label: 'Discuss trust requirements',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'Preview, confirmation, and auditability remain part of the restore story.',
|
||||
'Public claims stay narrower than internal ambition.',
|
||||
'No fake compliance theater for launch.',
|
||||
],
|
||||
};
|
||||
|
||||
export const securityPrinciples: TrustPrincipleContent[] = [
|
||||
{
|
||||
title: 'Safer changes are a product rule, not an afterthought.',
|
||||
description:
|
||||
'Destructive or high-risk flows are framed around preview, validation, and explicit confirmation instead of one-click confidence theater.',
|
||||
note: 'Trust starts with operator discipline.',
|
||||
},
|
||||
{
|
||||
title: 'Operational evidence stays tied to the real object and version.',
|
||||
description:
|
||||
'Findings, exceptions, and reviews remain anchored to observable tenant state so the trust story is defensible when someone asks for proof.',
|
||||
note: 'Evidence should stay attributable.',
|
||||
},
|
||||
{
|
||||
title: 'Launch messaging stays within substantiated boundaries.',
|
||||
description:
|
||||
'The public site avoids promising certifications, guarantees, or full automation outcomes that are not yet appropriate to claim.',
|
||||
note: 'Restraint is part of credibility.',
|
||||
},
|
||||
];
|
||||
|
||||
export const securityTrustNotes: CalloutContent[] = [
|
||||
{
|
||||
eyebrow: 'Substantiated public posture',
|
||||
title: 'Substantiated public posture',
|
||||
description:
|
||||
'The launch story focuses on product shape and operator safeguards: inventory truth, immutable snapshots, safer restore flows, drift visibility, and review support.',
|
||||
tone: 'accent',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Sensitive connections',
|
||||
title: 'Sensitive Microsoft connections should be explained carefully.',
|
||||
description:
|
||||
'The public site acknowledges Graph- and tenant-facing access without pretending the site itself is part of the runtime trust boundary.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'What we will not say',
|
||||
title: 'No blanket assurances and no vague “fully automated governance” language.',
|
||||
description:
|
||||
'Trust pages lose credibility quickly when they substitute slogans for the actual controls and workflows a buyer will later inspect.',
|
||||
tone: 'subtle',
|
||||
},
|
||||
];
|
||||
90
apps/website/src/content/pages/solutions.ts
Normal file
90
apps/website/src/content/pages/solutions.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import type {
|
||||
AudienceRowContent,
|
||||
FeatureItemContent,
|
||||
HeroContent,
|
||||
PageSeo,
|
||||
} from '@/types/site';
|
||||
|
||||
export const solutionsSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Solutions',
|
||||
description:
|
||||
'TenantAtlas fits MSP and enterprise IT teams differently, and the public story should make those operating-model differences explicit.',
|
||||
path: '/solutions',
|
||||
};
|
||||
|
||||
export const solutionsHero: HeroContent = {
|
||||
eyebrow: 'Audience fit',
|
||||
title: 'Show how TenantAtlas fits MSP delivery teams and enterprise operators without collapsing them into one generic story.',
|
||||
description:
|
||||
'The product helps different organizations answer similar governance questions, but the surrounding workflow, accountability, and evidence needs are not identical. The site should acknowledge that directly.',
|
||||
primaryCta: {
|
||||
href: '/integrations',
|
||||
label: 'Review the ecosystem fit',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/contact',
|
||||
label: 'Talk through your evaluation path',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'Separate MSP and enterprise language on purpose.',
|
||||
'Keep the product story stable while the buying context changes.',
|
||||
'Avoid forcing every visitor through the same generic motion.',
|
||||
],
|
||||
};
|
||||
|
||||
export const solutionsAudiences: AudienceRowContent[] = [
|
||||
{
|
||||
audience: 'MSP',
|
||||
title: 'MSP operating model',
|
||||
description:
|
||||
'Managed service providers need tenant-scoped operational truth, repeatable review workflows, and a way to explain change history to customers without drowning in manual evidence gathering.',
|
||||
bullets: [
|
||||
'Keep per-tenant history and restore posture reviewable during service delivery.',
|
||||
'Support a higher tempo of customer change while preserving a clean audit story.',
|
||||
'Give account and delivery teams a shared language for exceptions, findings, and follow-up.',
|
||||
],
|
||||
cta: {
|
||||
href: '/contact',
|
||||
label: 'Discuss MSP delivery fit',
|
||||
variant: 'secondary',
|
||||
},
|
||||
},
|
||||
{
|
||||
audience: 'Enterprise IT',
|
||||
title: 'Enterprise IT operating model',
|
||||
description:
|
||||
'Internal IT and security teams need durable version truth, change visibility, and review evidence that can stand up to operational leadership, audit, and cross-team scrutiny.',
|
||||
bullets: [
|
||||
'Reduce uncertainty around who changed what and when across the Microsoft tenant surface.',
|
||||
'Support internal review packs, exception handling, and evidence collection without fragmented tooling.',
|
||||
'Keep restore and remediation conversations grounded in the current tenant state and the relevant history.',
|
||||
],
|
||||
cta: {
|
||||
href: '/security-trust',
|
||||
label: 'Inspect the trust posture',
|
||||
variant: 'secondary',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const solutionsSignals: FeatureItemContent[] = [
|
||||
{
|
||||
eyebrow: 'Why buyers care',
|
||||
title: 'The product is serious about both velocity and control.',
|
||||
description:
|
||||
'Teams can move quickly without giving up visibility, confirmation discipline, or explainability when a risky change needs review.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Where it lands',
|
||||
title: 'The product belongs in the operating layer, not just the reporting layer.',
|
||||
description:
|
||||
'Visitors should understand that TenantAtlas helps teams make safer decisions about configuration state rather than merely summarize activity afterward.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'How it reads',
|
||||
title: 'The story changes by audience, but the product truth does not.',
|
||||
description:
|
||||
'MSP and enterprise readers see their own operating concerns reflected without the site inventing two different products.',
|
||||
},
|
||||
];
|
||||
60
apps/website/src/content/pages/terms.ts
Normal file
60
apps/website/src/content/pages/terms.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
||||
|
||||
export const termsSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Terms',
|
||||
description:
|
||||
'Website terms for the public TenantAtlas surface, covering informational use of the site and the limits of public product statements.',
|
||||
path: '/terms',
|
||||
};
|
||||
|
||||
export const termsHero: HeroContent = {
|
||||
eyebrow: 'Website terms',
|
||||
title: 'Website terms for the public TenantAtlas surface.',
|
||||
description:
|
||||
'These terms describe the public website itself: informational use of the content, basic conduct expectations, and the fact that a public product site is not the same thing as a signed service agreement.',
|
||||
primaryCta: {
|
||||
href: '/contact',
|
||||
label: 'Return to contact',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/privacy',
|
||||
label: 'Review privacy',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'Public copy explains the product but does not replace commercial agreements.',
|
||||
'The site is for evaluation and information, not operational control of a tenant.',
|
||||
'Any future service commitment belongs in explicit signed terms.',
|
||||
],
|
||||
};
|
||||
|
||||
export const termsSections: LegalSection[] = [
|
||||
{
|
||||
title: 'Informational website use',
|
||||
body: [
|
||||
'The public TenantAtlas website is provided to explain the product category, trust posture, integrations direction, and contact path for evaluation conversations.',
|
||||
'Nothing on the public site should be interpreted as a guarantee of product availability, certification, or commercial commitment unless it is later confirmed in signed agreements.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Reasonable reliance',
|
||||
body: [
|
||||
'Visitors may use the public site to understand the product and decide whether to start a conversation, but they should not rely on public marketing pages as the sole source of contractual or implementation truth.',
|
||||
'Detailed service commitments, security terms, and procurement obligations belong in later commercial and legal documentation.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Acceptable conduct',
|
||||
body: [
|
||||
'Visitors should use the public website lawfully and should not attempt to interfere with the availability or integrity of the public site.',
|
||||
'The public website is not a runtime administration surface and should not be treated as one.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Questions',
|
||||
body: [
|
||||
'Questions about the public website terms, privacy, or future product legal materials can be routed through the public contact path.',
|
||||
'The legal hub remains the public anchor for later launch-ready legal disclosures.',
|
||||
],
|
||||
},
|
||||
];
|
||||
@ -1,15 +1,26 @@
|
||||
---
|
||||
import '../styles/global.css';
|
||||
|
||||
import { siteMetadata } from '@/lib/site';
|
||||
|
||||
interface Props {
|
||||
canonicalUrl?: string;
|
||||
description?: string;
|
||||
openGraphDescription?: string;
|
||||
openGraphTitle?: string;
|
||||
robots?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
description = 'TenantPilot keeps Intune governance observable, reviewable, and safe to operate.',
|
||||
title = 'TenantPilot',
|
||||
canonicalUrl,
|
||||
description = siteMetadata.siteDescription,
|
||||
robots = 'index,follow',
|
||||
title = `${siteMetadata.siteName} | ${siteMetadata.siteTagline}`,
|
||||
} = Astro.props;
|
||||
|
||||
const openGraphTitle = Astro.props.openGraphTitle ?? title;
|
||||
const openGraphDescription = Astro.props.openGraphDescription ?? description;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
@ -17,11 +28,22 @@ const {
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content={description} />
|
||||
<meta name="robots" content={robots} />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<meta property="og:site_name" content={siteMetadata.siteName} />
|
||||
<meta property="og:title" content={openGraphTitle} />
|
||||
<meta property="og:description" content={openGraphDescription} />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={openGraphTitle} />
|
||||
<meta name="twitter:description" content={openGraphDescription} />
|
||||
{canonicalUrl && <link rel="canonical" href={canonicalUrl} />}
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#content">Skip to content</a>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
27
apps/website/src/lib/seo.ts
Normal file
27
apps/website/src/lib/seo.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { coreRoutes, siteMetadata } from '@/lib/site';
|
||||
import type { PageSeo } from '@/types/site';
|
||||
|
||||
export interface ResolvedSeo extends PageSeo {
|
||||
canonicalUrl: string;
|
||||
ogDescription: string;
|
||||
ogTitle: string;
|
||||
robots: string;
|
||||
}
|
||||
|
||||
export function buildCanonicalUrl(path: string): string {
|
||||
return new URL(path, siteMetadata.siteUrl).toString();
|
||||
}
|
||||
|
||||
export function resolveSeo(seo: PageSeo): ResolvedSeo {
|
||||
return {
|
||||
...seo,
|
||||
canonicalUrl: buildCanonicalUrl(seo.path),
|
||||
ogDescription: seo.ogDescription ?? seo.description,
|
||||
ogTitle: seo.ogTitle ?? seo.title,
|
||||
robots: 'index,follow',
|
||||
};
|
||||
}
|
||||
|
||||
export function sitemapEntries(): string[] {
|
||||
return [...coreRoutes].map((path) => buildCanonicalUrl(path));
|
||||
}
|
||||
76
apps/website/src/lib/site.ts
Normal file
76
apps/website/src/lib/site.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import type {
|
||||
CtaLink,
|
||||
FooterNavigationGroup,
|
||||
NavigationItem,
|
||||
SiteMetadata,
|
||||
} from '@/types/site';
|
||||
|
||||
export const siteMetadata: SiteMetadata = {
|
||||
siteName: 'TenantAtlas',
|
||||
siteTagline: 'Governance of record for Microsoft tenant operations.',
|
||||
siteDescription:
|
||||
'TenantAtlas helps MSP and enterprise teams keep Microsoft tenant change history observable, reviewable, and safer to operate.',
|
||||
siteUrl: import.meta.env.PUBLIC_SITE_URL ?? 'https://tenantatlas.example',
|
||||
};
|
||||
|
||||
export const primaryNavigation: NavigationItem[] = [
|
||||
{ href: '/product', label: 'Product', description: 'Understand the operating model.' },
|
||||
{ href: '/solutions', label: 'Solutions', description: 'See the fit for MSP and enterprise teams.' },
|
||||
{ href: '/security-trust', label: 'Security & Trust', description: 'Review the product posture.' },
|
||||
{ href: '/integrations', label: 'Integrations', description: 'Inspect the real ecosystem fit.' },
|
||||
{ href: '/contact', label: 'Contact', description: 'Reach the team for a working session.' },
|
||||
];
|
||||
|
||||
export const footerNavigationGroups: FooterNavigationGroup[] = [
|
||||
{
|
||||
title: 'Explore',
|
||||
items: [
|
||||
{ href: '/', label: 'Home' },
|
||||
{ href: '/product', label: 'Product' },
|
||||
{ href: '/solutions', label: 'Solutions' },
|
||||
{ href: '/security-trust', label: 'Security & Trust' },
|
||||
{ href: '/integrations', label: 'Integrations' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Next step',
|
||||
items: [
|
||||
{ href: '/contact', label: 'Contact / Demo' },
|
||||
{ href: '/legal', label: 'Legal' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Legal',
|
||||
items: [
|
||||
{ href: '/privacy', label: 'Privacy' },
|
||||
{ href: '/terms', label: 'Terms' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const contactCta: CtaLink = {
|
||||
href: '/contact',
|
||||
label: 'Request a working session',
|
||||
helper: 'Bring your governance questions, rollout concerns, or evaluation goals.',
|
||||
variant: 'primary',
|
||||
};
|
||||
|
||||
export const coreRoutes = [
|
||||
'/',
|
||||
'/product',
|
||||
'/solutions',
|
||||
'/security-trust',
|
||||
'/integrations',
|
||||
'/contact',
|
||||
'/legal',
|
||||
'/privacy',
|
||||
'/terms',
|
||||
] as const;
|
||||
|
||||
export function isActiveNavigationPath(currentPath: string, href: string): boolean {
|
||||
if (href === '/') {
|
||||
return currentPath === '/';
|
||||
}
|
||||
|
||||
return currentPath === href || currentPath.startsWith(`${href}/`);
|
||||
}
|
||||
98
apps/website/src/pages/contact.astro
Normal file
98
apps/website/src/pages/contact.astro
Normal file
@ -0,0 +1,98 @@
|
||||
---
|
||||
import ContactPanel from '@/components/content/ContactPanel.astro';
|
||||
import DemoPrompt from '@/components/content/DemoPrompt.astro';
|
||||
import RichText from '@/components/content/RichText.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import Cluster from '@/components/primitives/Cluster.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Input from '@/components/primitives/Input.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import Textarea from '@/components/primitives/Textarea.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import {
|
||||
contactHero,
|
||||
contactLegalSections,
|
||||
contactPreview,
|
||||
contactPrompts,
|
||||
contactSeo,
|
||||
contactTopics,
|
||||
} from '@/content/pages/contact';
|
||||
---
|
||||
|
||||
<PageShell currentPath="/contact" title={contactSeo.title} description={contactSeo.description}>
|
||||
<PageHero
|
||||
hero={contactHero}
|
||||
calloutTitle="Qualified conversations beat anonymous form funnels."
|
||||
calloutDescription="The page should make it obvious who should reach out, why, and what a useful first exchange looks like."
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<Grid cols="3">
|
||||
<ContactPanel cta={contactHero.primaryCta} points={contactTopics} title="Who should get in touch" />
|
||||
{contactPrompts.map((prompt) => <DemoPrompt title={prompt.title} description={prompt.description} />)}
|
||||
</Grid>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="grid gap-6 lg:grid-cols-[1fr,0.8fr]">
|
||||
<Card>
|
||||
<SectionHeader
|
||||
eyebrow="Suggested note"
|
||||
title="Give the team enough context to make the first reply useful."
|
||||
description="The public path stays static in v0, but it can still help a serious buyer structure the first message."
|
||||
/>
|
||||
<div class="mt-6 space-y-4">
|
||||
<Input readonly value={contactPreview.topic} />
|
||||
<Textarea readonly rows={7} value={contactPreview.message} />
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card variant="accent">
|
||||
<SectionHeader
|
||||
eyebrow="Before you share details"
|
||||
title="Legal basics stay visible from the contact flow."
|
||||
description="Visitors should be able to inspect privacy and terms before they continue."
|
||||
/>
|
||||
<Cluster class="mt-6">
|
||||
<a
|
||||
href="/privacy"
|
||||
class="inline-flex min-h-11 items-center justify-center rounded-full border border-[color:var(--color-line)] bg-white/85 px-5 text-sm font-semibold text-[var(--color-ink-900)]"
|
||||
>
|
||||
Privacy
|
||||
</a>
|
||||
<a
|
||||
href="/terms"
|
||||
class="inline-flex min-h-11 items-center justify-center rounded-full border border-[color:var(--color-line)] bg-white/85 px-5 text-sm font-semibold text-[var(--color-ink-900)]"
|
||||
>
|
||||
Terms
|
||||
</a>
|
||||
<a
|
||||
href="/legal"
|
||||
class="inline-flex min-h-11 items-center justify-center rounded-full border border-[color:var(--color-line)] bg-white/85 px-5 text-sm font-semibold text-[var(--color-ink-900)]"
|
||||
>
|
||||
Legal
|
||||
</a>
|
||||
</Cluster>
|
||||
<div class="mt-6">
|
||||
<RichText sections={contactLegalSections} />
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<CTASection
|
||||
eyebrow="Next step"
|
||||
title="Move from a public introduction into the legal and product details that support a real evaluation."
|
||||
description="The contact route should never strand a serious buyer. The next path stays visible whether they need product context, privacy details, or website terms."
|
||||
primary={{ href: '/legal', label: 'Read the legal surface' }}
|
||||
secondary={{ href: '/product', label: 'Revisit the product model', variant: 'secondary' }}
|
||||
/>
|
||||
</PageShell>
|
||||
@ -1,66 +1,65 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import Callout from '@/components/content/Callout.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import FeatureGrid from '@/components/sections/FeatureGrid.astro';
|
||||
import LogoStrip from '@/components/sections/LogoStrip.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import {
|
||||
homeEcosystem,
|
||||
homeHero,
|
||||
homeMetrics,
|
||||
homePillars,
|
||||
homeProofBlocks,
|
||||
homeSeo,
|
||||
} from '@/content/pages/home';
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title="TenantPilot | Workspace Foundation"
|
||||
description="The first public TenantPilot website surface for workspace-safe Intune operations."
|
||||
>
|
||||
<main class="page-shell">
|
||||
<section class="hero">
|
||||
<p class="eyebrow">TenantPilot</p>
|
||||
<h1>One public website, one stable platform, one clear workspace model.</h1>
|
||||
<p class="lede">
|
||||
TenantPilot keeps Intune change management auditable for operators while the public
|
||||
website stays fast, static, and operationally separate from the Laravel platform.
|
||||
</p>
|
||||
<PageShell currentPath="/" title={homeSeo.title} description={homeSeo.description}>
|
||||
<PageHero
|
||||
hero={homeHero}
|
||||
metrics={homeMetrics}
|
||||
calloutTitle="Governance of record for Microsoft tenant operations."
|
||||
calloutDescription="The public story positions TenantAtlas as a trust-first system for version truth, safer restore posture, drift visibility, evidence, and review support."
|
||||
/>
|
||||
|
||||
<div class="hero-actions">
|
||||
<a class="primary-action" href="#workspace-model">View the workspace model</a>
|
||||
<a class="secondary-action" href="#boundaries">Review the isolation rules</a>
|
||||
<LogoStrip
|
||||
eyebrow="Ecosystem fit"
|
||||
title="Built around the Microsoft tenant reality buyers already need to govern."
|
||||
items={homeEcosystem}
|
||||
/>
|
||||
|
||||
<FeatureGrid
|
||||
eyebrow="Product pillars"
|
||||
title="Explain the product in connected pillars, not isolated promises."
|
||||
description="Each section of the site should help a first-time visitor understand why backup, restore, findings, evidence, and reviews belong together."
|
||||
items={homePillars}
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="space-y-8">
|
||||
<SectionHeader
|
||||
eyebrow="Public proof"
|
||||
title="A credible first reading should answer the buyer’s next two questions before they ask them."
|
||||
description="Why is this product category needed now, and why should anyone trust the story enough to continue?"
|
||||
/>
|
||||
<Grid cols="3">
|
||||
{homeProofBlocks.map((block) => <Callout content={block} />)}
|
||||
</Grid>
|
||||
</div>
|
||||
</section>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<section class="signal-grid" id="workspace-model" aria-label="Workspace foundations">
|
||||
<article class="signal-card">
|
||||
<p class="signal-label">Platform</p>
|
||||
<h2>Laravel stays in <code>apps/platform</code>.</h2>
|
||||
<p>
|
||||
Sail, Filament, Livewire, and deployment-sensitive runtime concerns remain
|
||||
platform-owned and unchanged.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="signal-card">
|
||||
<p class="signal-label">Website</p>
|
||||
<h2>Astro lives independently in <code>apps/website</code>.</h2>
|
||||
<p>
|
||||
Public pages build statically, run without Laravel, and keep their own dev and
|
||||
build outputs.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="signal-card">
|
||||
<p class="signal-label">Root</p>
|
||||
<h2>The repository root orchestrates without becoming an app.</h2>
|
||||
<p>
|
||||
Root scripts expose the official entry commands while app-local execution logic
|
||||
stays inside each app directory.
|
||||
</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="boundary-panel" id="boundaries">
|
||||
<div>
|
||||
<p class="eyebrow">Isolation</p>
|
||||
<h2>Builds, ports, and ownership stay intentionally separate.</h2>
|
||||
</div>
|
||||
|
||||
<ul class="boundary-list">
|
||||
<li>Website dev defaults to port 4321 and supports explicit port overrides.</li>
|
||||
<li>Platform Docker, queues, and Filament assets stay under the existing Sail flow.</li>
|
||||
<li>No shared package layer, CMS, or extra app surface is introduced in this slice.</li>
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
<CTASection
|
||||
eyebrow="Next step"
|
||||
title="Move from first-glance clarity into the deeper product story."
|
||||
description="From the Home page, visitors should be able to inspect the product model, review trust framing, or reach the contact path without guessing where to go next."
|
||||
primary={{ href: '/product', label: 'See the product model' }}
|
||||
secondary={{ href: '/contact', label: 'Start the working session', variant: 'secondary' }}
|
||||
/>
|
||||
</PageShell>
|
||||
|
||||
55
apps/website/src/pages/integrations.astro
Normal file
55
apps/website/src/pages/integrations.astro
Normal file
@ -0,0 +1,55 @@
|
||||
---
|
||||
import IntegrationBadge from '@/components/content/IntegrationBadge.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import FeatureGrid from '@/components/sections/FeatureGrid.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import {
|
||||
integrationEntries,
|
||||
integrationRules,
|
||||
integrationsHero,
|
||||
integrationsSeo,
|
||||
} from '@/content/pages/integrations';
|
||||
---
|
||||
|
||||
<PageShell currentPath="/integrations" title={integrationsSeo.title} description={integrationsSeo.description}>
|
||||
<PageHero
|
||||
hero={integrationsHero}
|
||||
calloutTitle="Real direction beats a longer wishlist."
|
||||
calloutDescription="The integrations page should reinforce where the product actually fits today and why those boundaries improve trust rather than limit it."
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="space-y-8">
|
||||
<SectionHeader
|
||||
eyebrow="Current direction"
|
||||
title="Show the systems that shape the product workflow today."
|
||||
description="This page should stay focused on the contracts and ecosystems that matter to Microsoft tenant governance work now."
|
||||
/>
|
||||
<Grid cols="2">
|
||||
{integrationEntries.map((item) => <IntegrationBadge item={item} />)}
|
||||
</Grid>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<FeatureGrid
|
||||
eyebrow="Page rules"
|
||||
title="Use the integrations page to clarify scope, not to perform ambition."
|
||||
description="The public site becomes more credible when it names the real ecosystem fit and avoids presenting speculative adjacencies as if they were launch truth."
|
||||
items={integrationRules}
|
||||
/>
|
||||
|
||||
<CTASection
|
||||
eyebrow="Next step"
|
||||
title="Turn ecosystem fit into a practical evaluation conversation."
|
||||
description="Once a buyer sees the Microsoft-centric fit, the next useful step is a working session about their current environment, governance needs, and rollout questions."
|
||||
primary={{ href: '/contact', label: 'Plan the working session' }}
|
||||
secondary={{ href: '/product', label: 'Revisit the product model', variant: 'secondary' }}
|
||||
/>
|
||||
</PageShell>
|
||||
75
apps/website/src/pages/legal.astro
Normal file
75
apps/website/src/pages/legal.astro
Normal file
@ -0,0 +1,75 @@
|
||||
---
|
||||
import RichText from '@/components/content/RichText.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import { legalHero, legalNoticeSections, legalSeo } from '@/content/pages/legal';
|
||||
---
|
||||
|
||||
<PageShell currentPath="/legal" title={legalSeo.title} description={legalSeo.description}>
|
||||
<PageHero
|
||||
hero={legalHero}
|
||||
calloutTitle="Public legal basics belong in one obvious place."
|
||||
calloutDescription="The legal hub keeps the conversion path honest by making privacy, terms, and notice routing easy to find before or during evaluation conversations."
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="space-y-8">
|
||||
<SectionHeader
|
||||
eyebrow="Available now"
|
||||
title="Privacy and terms are published as standalone routes."
|
||||
description="The legal hub should work as an index and as the public home for launch-required legal notices."
|
||||
/>
|
||||
<Grid cols="3">
|
||||
<Card>
|
||||
<h3 class="m-0 text-2xl font-semibold text-[var(--color-ink-900)]">Privacy</h3>
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">
|
||||
Review how the public contact path handles inquiry information and what this static website does not claim to process.
|
||||
</p>
|
||||
<a class="mt-5 inline-flex text-sm font-semibold text-[var(--color-brand)]" href="/privacy">
|
||||
Privacy
|
||||
</a>
|
||||
</Card>
|
||||
<Card>
|
||||
<h3 class="m-0 text-2xl font-semibold text-[var(--color-ink-900)]">Terms</h3>
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">
|
||||
Read the website terms that explain the public-site scope and why marketing pages do not replace signed agreements.
|
||||
</p>
|
||||
<a class="mt-5 inline-flex text-sm font-semibold text-[var(--color-brand)]" href="/terms">
|
||||
Terms
|
||||
</a>
|
||||
</Card>
|
||||
<Card variant="accent">
|
||||
<h3 class="m-0 text-2xl font-semibold text-[var(--color-ink-900)]">Public legal notice</h3>
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">
|
||||
This hub also owns the future launch-ready operator identity and jurisdiction-specific disclosure section.
|
||||
</p>
|
||||
<a class="mt-5 inline-flex text-sm font-semibold text-[var(--color-brand)]" href="#public-legal-notice">
|
||||
Legal notice
|
||||
</a>
|
||||
</Card>
|
||||
</Grid>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<Section id="public-legal-notice">
|
||||
<Container wide>
|
||||
<RichText sections={legalNoticeSections} />
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<CTASection
|
||||
eyebrow="Continue"
|
||||
title="Return to the contact path once the legal basics are clear."
|
||||
description="The legal surface should support a qualified conversation, not interrupt it."
|
||||
primary={{ href: '/contact', label: 'Return to contact' }}
|
||||
secondary={{ href: '/product', label: 'Revisit the product model', variant: 'secondary' }}
|
||||
/>
|
||||
</PageShell>
|
||||
31
apps/website/src/pages/privacy.astro
Normal file
31
apps/website/src/pages/privacy.astro
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
import RichText from '@/components/content/RichText.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import { privacyHero, privacySections, privacySeo } from '@/content/pages/privacy';
|
||||
---
|
||||
|
||||
<PageShell currentPath="/privacy" title={privacySeo.title} description={privacySeo.description}>
|
||||
<PageHero
|
||||
hero={privacyHero}
|
||||
calloutTitle="Public-site privacy should stay narrow and readable."
|
||||
calloutDescription="The page explains the website and inquiry path clearly without pretending to be the product’s full data-processing documentation."
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<RichText sections={privacySections} />
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<CTASection
|
||||
eyebrow="Next step"
|
||||
title="Return to the product or contact flow after reviewing public-site privacy."
|
||||
description="Visitors should be able to move back into the evaluation path without losing context."
|
||||
primary={{ href: '/contact', label: 'Return to contact' }}
|
||||
secondary={{ href: '/terms', label: 'Review website terms', variant: 'secondary' }}
|
||||
/>
|
||||
</PageShell>
|
||||
61
apps/website/src/pages/product.astro
Normal file
61
apps/website/src/pages/product.astro
Normal file
@ -0,0 +1,61 @@
|
||||
---
|
||||
import Callout from '@/components/content/Callout.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import FeatureGrid from '@/components/sections/FeatureGrid.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import {
|
||||
productHero,
|
||||
productMetrics,
|
||||
productModelBlocks,
|
||||
productNarrative,
|
||||
productSeo,
|
||||
} from '@/content/pages/product';
|
||||
---
|
||||
|
||||
<PageShell currentPath="/product" title={productSeo.title} description={productSeo.description}>
|
||||
<PageHero
|
||||
hero={productHero}
|
||||
metrics={productMetrics}
|
||||
calloutTitle="Connected governance model"
|
||||
calloutDescription="TenantAtlas connects present-state inventory, immutable snapshots, restore posture, drift, exceptions, and evidence so teams can explain what happened before they decide what to do next."
|
||||
/>
|
||||
|
||||
<FeatureGrid
|
||||
eyebrow="Connected governance model"
|
||||
title="Treat the product as one operating system for safer tenant change management."
|
||||
description="This page explains how the pieces fit together so visitors do not mistake the product for a loose collection of backup, reporting, and restore features."
|
||||
items={productModelBlocks}
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="space-y-8">
|
||||
<SectionHeader
|
||||
eyebrow="Narrative"
|
||||
title="Explain the operator journey, not just the capabilities."
|
||||
description="The public product page should make it obvious how the product helps a team move from current-state understanding into reviewable action."
|
||||
/>
|
||||
<Grid cols="3">
|
||||
{productNarrative.map((block) => <Callout content={block} />)}
|
||||
</Grid>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<CTASection
|
||||
eyebrow="Continue"
|
||||
title="Inspect whether the operating model fits your audience and workflow."
|
||||
description="The next useful questions are who the product is for, how trust claims stay grounded, and what a working conversation with the team should cover."
|
||||
primary={{ href: '/solutions', label: 'See audience fit' }}
|
||||
secondary={{
|
||||
href: '/contact',
|
||||
label: 'Talk through your current operating model',
|
||||
variant: 'secondary',
|
||||
}}
|
||||
/>
|
||||
</PageShell>
|
||||
59
apps/website/src/pages/security-trust.astro
Normal file
59
apps/website/src/pages/security-trust.astro
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
import Callout from '@/components/content/Callout.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import TrustGrid from '@/components/sections/TrustGrid.astro';
|
||||
import {
|
||||
securityPrinciples,
|
||||
securityTrustHero,
|
||||
securityTrustNotes,
|
||||
securityTrustSeo,
|
||||
} from '@/content/pages/security-trust';
|
||||
---
|
||||
|
||||
<PageShell
|
||||
currentPath="/security-trust"
|
||||
title={securityTrustSeo.title}
|
||||
description={securityTrustSeo.description}
|
||||
>
|
||||
<PageHero
|
||||
hero={securityTrustHero}
|
||||
calloutTitle="Trust-first, not trust-theater."
|
||||
calloutDescription="The page should help technical buyers see the operator safeguards and the intentional limits of the launch story before they hear a sales pitch."
|
||||
/>
|
||||
|
||||
<TrustGrid
|
||||
eyebrow="Product posture"
|
||||
title="Operational trust starts with the way the product handles risky decisions."
|
||||
description="The trust page should explain the guardrails that matter to a serious buyer: previewability, attributable change history, evidence linkage, and restrained public claims."
|
||||
items={securityPrinciples}
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="space-y-8">
|
||||
<SectionHeader
|
||||
eyebrow="Public messaging"
|
||||
title="Substantiated public posture"
|
||||
description="Keep the public trust story within the set of claims the team can support at launch."
|
||||
/>
|
||||
<Grid cols="3">
|
||||
{securityTrustNotes.map((note) => <Callout content={note} />)}
|
||||
</Grid>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<CTASection
|
||||
eyebrow="Next step"
|
||||
title="Legal clarity and conversation path should stay reachable from the trust page."
|
||||
description="A buyer evaluating trust should be able to move directly to public legal information or a working discussion without friction."
|
||||
primary={{ href: '/legal', label: 'Read the legal surface' }}
|
||||
secondary={{ href: '/contact', label: 'Discuss trust requirements', variant: 'secondary' }}
|
||||
/>
|
||||
</PageShell>
|
||||
20
apps/website/src/pages/sitemap.xml.ts
Normal file
20
apps/website/src/pages/sitemap.xml.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
|
||||
import { sitemapEntries } from '@/lib/seo';
|
||||
|
||||
export const GET: APIRoute = () => {
|
||||
const urls = sitemapEntries()
|
||||
.map((url) => ` <url><loc>${url}</loc></url>`)
|
||||
.join('\n');
|
||||
|
||||
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${urls}
|
||||
</urlset>`;
|
||||
|
||||
return new Response(body, {
|
||||
headers: {
|
||||
'Content-Type': 'application/xml; charset=utf-8',
|
||||
},
|
||||
});
|
||||
};
|
||||
55
apps/website/src/pages/solutions.astro
Normal file
55
apps/website/src/pages/solutions.astro
Normal file
@ -0,0 +1,55 @@
|
||||
---
|
||||
import AudienceRow from '@/components/content/AudienceRow.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import FeatureGrid from '@/components/sections/FeatureGrid.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import {
|
||||
solutionsAudiences,
|
||||
solutionsHero,
|
||||
solutionsSeo,
|
||||
solutionsSignals,
|
||||
} from '@/content/pages/solutions';
|
||||
---
|
||||
|
||||
<PageShell currentPath="/solutions" title={solutionsSeo.title} description={solutionsSeo.description}>
|
||||
<PageHero
|
||||
hero={solutionsHero}
|
||||
calloutTitle="Audience-specific fit without product sprawl."
|
||||
calloutDescription="The public site can speak differently to MSP and enterprise visitors while staying anchored to the same product truth."
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="space-y-8">
|
||||
<SectionHeader
|
||||
eyebrow="Operating models"
|
||||
title="Separate the delivery context clearly."
|
||||
description="Visitors should be able to recognize themselves in the page quickly, without translating a generic story into their own workflow."
|
||||
/>
|
||||
<Grid cols="2">
|
||||
{solutionsAudiences.map((item) => <AudienceRow item={item} />)}
|
||||
</Grid>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<FeatureGrid
|
||||
eyebrow="Buying signal"
|
||||
title="Give the buyer a concrete reason to keep evaluating."
|
||||
description="The goal is not to decorate the page with vertical tags. The goal is to show why the product belongs in the operating model for that audience."
|
||||
items={solutionsSignals}
|
||||
/>
|
||||
|
||||
<CTASection
|
||||
eyebrow="Continue"
|
||||
title="Inspect the ecosystem fit after you understand the audience fit."
|
||||
description="Once a visitor sees the product reflected in their operating model, the next useful question is how it fits the surrounding Microsoft tenant environment."
|
||||
primary={{ href: '/integrations', label: 'Review the ecosystem fit' }}
|
||||
secondary={{ href: '/contact', label: 'Talk through your evaluation path', variant: 'secondary' }}
|
||||
/>
|
||||
</PageShell>
|
||||
31
apps/website/src/pages/terms.astro
Normal file
31
apps/website/src/pages/terms.astro
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
import RichText from '@/components/content/RichText.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import { termsHero, termsSections, termsSeo } from '@/content/pages/terms';
|
||||
---
|
||||
|
||||
<PageShell currentPath="/terms" title={termsSeo.title} description={termsSeo.description}>
|
||||
<PageHero
|
||||
hero={termsHero}
|
||||
calloutTitle="Public terms define the site, not a service contract."
|
||||
calloutDescription="The page keeps the public website honest about what it can explain and what still belongs in later commercial/legal paperwork."
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<RichText sections={termsSections} />
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<CTASection
|
||||
eyebrow="Next step"
|
||||
title="Move back into privacy or contact once the website terms are clear."
|
||||
description="The legal path should stay connected to the rest of the evaluation journey."
|
||||
primary={{ href: '/contact', label: 'Return to contact' }}
|
||||
secondary={{ href: '/privacy', label: 'Review privacy', variant: 'secondary' }}
|
||||
/>
|
||||
</PageShell>
|
||||
@ -1,221 +1,157 @@
|
||||
@import "tailwindcss";
|
||||
@import "./tokens.css";
|
||||
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--bg: #f6efe5;
|
||||
--bg-accent: #fffdf9;
|
||||
--surface: rgba(255, 255, 255, 0.74);
|
||||
--surface-strong: rgba(255, 255, 255, 0.92);
|
||||
--ink: #17120f;
|
||||
--muted: #66584d;
|
||||
--line: rgba(23, 18, 15, 0.12);
|
||||
--accent: #cc5f2c;
|
||||
--accent-deep: #8b3820;
|
||||
--shadow: 0 30px 80px rgba(103, 52, 33, 0.16);
|
||||
font-family: "Avenir Next", "Segoe UI", sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
--color-ink-900: #11243a;
|
||||
--color-ink-800: #233a53;
|
||||
--color-copy: #42556a;
|
||||
--color-line: rgba(17, 36, 58, 0.14);
|
||||
--color-panel: rgba(255, 255, 255, 0.82);
|
||||
--color-panel-strong: rgba(255, 255, 255, 0.95);
|
||||
--color-panel-soft: rgba(243, 247, 251, 0.86);
|
||||
--color-brand: #2f6fb7;
|
||||
--color-brand-soft: rgba(47, 111, 183, 0.12);
|
||||
--color-signal: #3b8b78;
|
||||
--color-warm: #af6d43;
|
||||
--shadow-panel: 0 24px 80px rgba(17, 36, 58, 0.12);
|
||||
--shadow-soft: 0 18px 48px rgba(17, 36, 58, 0.08);
|
||||
}
|
||||
|
||||
html {
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(255, 201, 149, 0.55), transparent 34%),
|
||||
radial-gradient(circle at right 12% top 10%, rgba(255, 145, 96, 0.18), transparent 24%),
|
||||
linear-gradient(180deg, #fffaf3 0%, var(--bg) 58%, #efe3d5 100%);
|
||||
radial-gradient(circle at top left, rgba(255, 255, 255, 0.92), transparent 32%),
|
||||
radial-gradient(circle at top right, rgba(92, 149, 215, 0.18), transparent 28%),
|
||||
linear-gradient(180deg, #f6f3ee 0%, #edf2f7 56%, #f3f7fb 100%);
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
color: var(--ink);
|
||||
margin: 0;
|
||||
font-family: var(--font-sans);
|
||||
color: var(--color-ink-900);
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "SFMono-Regular", "SF Mono", "IBM Plex Mono", monospace;
|
||||
font-size: 0.92em;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.page-shell {
|
||||
width: min(1120px, calc(100% - 2rem));
|
||||
margin: 0 auto;
|
||||
padding: 4.5rem 0 5rem;
|
||||
::selection {
|
||||
background: rgba(47, 111, 183, 0.18);
|
||||
color: var(--color-ink-900);
|
||||
}
|
||||
|
||||
.hero,
|
||||
.signal-card,
|
||||
.boundary-panel {
|
||||
backdrop-filter: blur(18px);
|
||||
box-shadow: var(--shadow);
|
||||
:focus-visible {
|
||||
outline: 3px solid rgba(47, 111, 183, 0.32);
|
||||
outline-offset: 4px;
|
||||
}
|
||||
|
||||
.hero {
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.surface-shell {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: clamp(2rem, 4vw, 4.5rem);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 2rem;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.88), rgba(255, 247, 239, 0.72)),
|
||||
linear-gradient(120deg, rgba(204, 95, 44, 0.08), rgba(255, 255, 255, 0));
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.hero::after {
|
||||
content: "";
|
||||
.surface-shell::before {
|
||||
position: absolute;
|
||||
inset: auto -8rem -8rem auto;
|
||||
width: 18rem;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 999px;
|
||||
background: radial-gradient(circle, rgba(204, 95, 44, 0.22), transparent 72%);
|
||||
}
|
||||
|
||||
.eyebrow,
|
||||
.signal-label {
|
||||
margin: 0 0 0.9rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.18em;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
color: var(--accent-deep);
|
||||
}
|
||||
|
||||
.hero h1,
|
||||
.boundary-panel h2,
|
||||
.signal-card h2 {
|
||||
margin: 0;
|
||||
font-family: "Iowan Old Style", "Palatino Linotype", serif;
|
||||
line-height: 0.95;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
max-width: 13ch;
|
||||
font-size: clamp(3rem, 8vw, 6rem);
|
||||
}
|
||||
|
||||
.lede {
|
||||
max-width: 46rem;
|
||||
margin: 1.5rem 0 0;
|
||||
font-size: clamp(1.05rem, 2vw, 1.35rem);
|
||||
line-height: 1.7;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.primary-action,
|
||||
.secondary-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 3.25rem;
|
||||
padding: 0.9rem 1.4rem;
|
||||
inset: 0;
|
||||
z-index: -2;
|
||||
content: "";
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.65), transparent 16%),
|
||||
radial-gradient(circle at 20% 20%, rgba(255, 255, 255, 0.7), transparent 28%);
|
||||
}
|
||||
|
||||
.surface-shell::after {
|
||||
position: absolute;
|
||||
inset: 1rem;
|
||||
z-index: -1;
|
||||
content: "";
|
||||
border: 1px solid rgba(17, 36, 58, 0.04);
|
||||
border-radius: 2rem;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
z-index: 40;
|
||||
transform: translateY(-200%);
|
||||
border-radius: 999px;
|
||||
background: var(--color-ink-900);
|
||||
padding: 0.75rem 1rem;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
transition:
|
||||
transform 180ms ease,
|
||||
box-shadow 180ms ease,
|
||||
background-color 180ms ease;
|
||||
transition: transform 140ms ease;
|
||||
}
|
||||
|
||||
.primary-action {
|
||||
background: var(--ink);
|
||||
color: #fff7f1;
|
||||
.skip-link:focus {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.secondary-action {
|
||||
border: 1px solid rgba(23, 18, 15, 0.12);
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
.glass-panel {
|
||||
background: linear-gradient(180deg, var(--color-panel-strong), var(--color-panel));
|
||||
box-shadow: var(--shadow-panel);
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.primary-action:hover,
|
||||
.secondary-action:hover {
|
||||
transform: translateY(-1px);
|
||||
.section-divider {
|
||||
border-top: 1px solid rgba(17, 36, 58, 0.08);
|
||||
}
|
||||
|
||||
.signal-grid {
|
||||
display: grid;
|
||||
gap: 1.25rem;
|
||||
margin-top: 1.4rem;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
.legal-prose p {
|
||||
margin: 0;
|
||||
color: var(--color-copy);
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.signal-card,
|
||||
.boundary-panel {
|
||||
padding: 1.6rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 1.5rem;
|
||||
background: var(--surface);
|
||||
.legal-prose p + p {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.signal-card h2 {
|
||||
font-size: clamp(1.55rem, 3vw, 2.1rem);
|
||||
}
|
||||
|
||||
.signal-card p:last-child,
|
||||
.boundary-list {
|
||||
.legal-prose ul {
|
||||
margin: 1rem 0 0;
|
||||
color: var(--muted);
|
||||
line-height: 1.7;
|
||||
padding-left: 1.1rem;
|
||||
color: var(--color-copy);
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.boundary-panel {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
margin-top: 1.25rem;
|
||||
background: var(--surface-strong);
|
||||
.legal-prose li + li {
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
|
||||
.boundary-panel h2 {
|
||||
font-size: clamp(2rem, 4vw, 3.1rem);
|
||||
.motion-rise {
|
||||
animation: rise-in 520ms ease both;
|
||||
}
|
||||
|
||||
.boundary-list {
|
||||
padding-left: 1.2rem;
|
||||
@keyframes rise-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(16px);
|
||||
}
|
||||
|
||||
.boundary-list li + li {
|
||||
margin-top: 0.7rem;
|
||||
}
|
||||
|
||||
@media (max-width: 920px) {
|
||||
.signal-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.page-shell {
|
||||
width: min(100% - 1.25rem, 1120px);
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 3rem;
|
||||
}
|
||||
|
||||
.hero,
|
||||
.signal-card,
|
||||
.boundary-panel {
|
||||
border-radius: 1.3rem;
|
||||
}
|
||||
|
||||
.hero {
|
||||
padding: 1.4rem;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.primary-action,
|
||||
.secondary-action {
|
||||
width: 100%;
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
19
apps/website/src/styles/tokens.css
Normal file
19
apps/website/src/styles/tokens.css
Normal file
@ -0,0 +1,19 @@
|
||||
@theme {
|
||||
--font-sans: "Avenir Next", "Segoe UI", sans-serif;
|
||||
--font-display: "Iowan Old Style", "Palatino Linotype", serif;
|
||||
--font-mono: "IBM Plex Mono", "SFMono-Regular", monospace;
|
||||
|
||||
--color-shell-50: oklch(0.985 0.01 86);
|
||||
--color-shell-100: oklch(0.97 0.015 85);
|
||||
--color-shell-200: oklch(0.935 0.025 84);
|
||||
--color-shell-300: oklch(0.88 0.04 78);
|
||||
--color-shell-900: oklch(0.19 0.03 65);
|
||||
--color-shell-950: oklch(0.14 0.025 62);
|
||||
--color-brand-400: oklch(0.76 0.09 210);
|
||||
--color-brand-500: oklch(0.68 0.11 218);
|
||||
--color-brand-700: oklch(0.49 0.11 228);
|
||||
--color-signal-400: oklch(0.78 0.1 162);
|
||||
--color-signal-700: oklch(0.52 0.08 170);
|
||||
--color-warm-300: oklch(0.87 0.05 53);
|
||||
--color-warm-500: oklch(0.69 0.09 48);
|
||||
}
|
||||
105
apps/website/src/types/site.ts
Normal file
105
apps/website/src/types/site.ts
Normal file
@ -0,0 +1,105 @@
|
||||
export type ButtonVariant = 'primary' | 'secondary' | 'ghost';
|
||||
export type PageRole =
|
||||
| 'home'
|
||||
| 'product'
|
||||
| 'solutions'
|
||||
| 'trust'
|
||||
| 'integrations'
|
||||
| 'contact'
|
||||
| 'legal';
|
||||
|
||||
export interface CtaLink {
|
||||
href: string;
|
||||
label: string;
|
||||
helper?: string;
|
||||
target?: '_blank' | '_self';
|
||||
variant?: ButtonVariant;
|
||||
}
|
||||
|
||||
export interface NavigationItem {
|
||||
href: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface FooterNavigationGroup {
|
||||
items: NavigationItem[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface SiteMetadata {
|
||||
siteDescription: string;
|
||||
siteName: string;
|
||||
siteTagline: string;
|
||||
siteUrl: string;
|
||||
}
|
||||
|
||||
export interface PageSeo {
|
||||
description: string;
|
||||
ogDescription?: string;
|
||||
ogTitle?: string;
|
||||
path: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface HeroContent {
|
||||
description: string;
|
||||
eyebrow: string;
|
||||
highlights?: string[];
|
||||
primaryCta: CtaLink;
|
||||
secondaryCta?: CtaLink;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface MetricItem {
|
||||
description: string;
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface FeatureItemContent {
|
||||
description: string;
|
||||
eyebrow?: string;
|
||||
href?: string;
|
||||
meta?: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface CalloutContent {
|
||||
description: string;
|
||||
eyebrow?: string;
|
||||
title: string;
|
||||
tone?: 'accent' | 'neutral' | 'subtle';
|
||||
}
|
||||
|
||||
export interface AudienceRowContent {
|
||||
audience: string;
|
||||
bullets: string[];
|
||||
cta?: CtaLink;
|
||||
description: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface TrustPrincipleContent {
|
||||
description: string;
|
||||
note?: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface IntegrationEntry {
|
||||
category: string;
|
||||
name: string;
|
||||
note?: string;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface LogoStripItem {
|
||||
label: string;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export interface LegalSection {
|
||||
body: string[];
|
||||
bullets?: string[];
|
||||
title: string;
|
||||
}
|
||||
57
apps/website/tests/smoke/contact-legal.spec.ts
Normal file
57
apps/website/tests/smoke/contact-legal.spec.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import {
|
||||
expectFooterLinks,
|
||||
expectPrimaryNavigation,
|
||||
expectShell,
|
||||
openMobileNavigation,
|
||||
visitPage,
|
||||
} from './smoke-helpers';
|
||||
|
||||
const coreRoutes = ['/', '/product', '/solutions', '/security-trust', '/integrations', '/contact'] as const;
|
||||
|
||||
test('contact page qualifies the conversation and keeps legal links reachable', async ({ page }) => {
|
||||
await visitPage(page, '/contact');
|
||||
await expectShell(page, 'Start a qualified working session instead of a generic demo request.');
|
||||
await expectPrimaryNavigation(page);
|
||||
await expectFooterLinks(page);
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'Email the TenantAtlas team' }).first()).toBeVisible();
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'Privacy' }).first()).toBeVisible();
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'Terms' }).first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('legal, privacy, and terms routes are published and linked', async ({ page }) => {
|
||||
await visitPage(page, '/legal');
|
||||
await expectShell(page, 'Legal access should stay one click away from the contact path.');
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'Privacy' }).first()).toBeVisible();
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'Terms' }).first()).toBeVisible();
|
||||
|
||||
await visitPage(page, '/privacy');
|
||||
await expectShell(page, 'Public-site privacy overview for TenantAtlas inquiries.');
|
||||
|
||||
await visitPage(page, '/terms');
|
||||
await expectShell(page, 'Website terms for the public TenantAtlas surface.');
|
||||
});
|
||||
|
||||
test('core pages keep contact and legal paths within reach', async ({ page }) => {
|
||||
for (const path of coreRoutes) {
|
||||
await visitPage(page, path);
|
||||
await expectFooterLinks(page);
|
||||
|
||||
if (path !== '/contact') {
|
||||
await expect(page.locator('main a[href="/contact"]').first()).toBeVisible();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test.describe('mobile navigation', () => {
|
||||
test.use({ viewport: { width: 390, height: 844 } });
|
||||
|
||||
test('mobile menu exposes the published contact and legal paths', async ({ page }) => {
|
||||
await visitPage(page, '/');
|
||||
await openMobileNavigation(page);
|
||||
await expect(page.getByRole('banner').getByRole('link', { name: /Contact/ }).first()).toBeVisible();
|
||||
await expect(page.getByRole('contentinfo').getByRole('link', { name: 'Privacy' })).toBeVisible();
|
||||
await expect(page.getByRole('contentinfo').getByRole('link', { name: 'Terms' })).toBeVisible();
|
||||
});
|
||||
});
|
||||
39
apps/website/tests/smoke/home-product.spec.ts
Normal file
39
apps/website/tests/smoke/home-product.spec.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import {
|
||||
expectFooterLinks,
|
||||
expectPrimaryNavigation,
|
||||
expectShell,
|
||||
visitPage,
|
||||
} from './smoke-helpers';
|
||||
|
||||
test('home explains the product category and exposes the next step', async ({ page }) => {
|
||||
await visitPage(page, '/');
|
||||
await expectShell(page, /TenantAtlas/);
|
||||
await expectPrimaryNavigation(page);
|
||||
await expectFooterLinks(page);
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Governance of record for Microsoft tenant operations.' }).first(),
|
||||
).toBeVisible();
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'See the product model' }).first()).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('main').getByRole('link', { name: 'Review the trust posture' }).first(),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('product explains the connected operating model instead of a loose feature list', async ({
|
||||
page,
|
||||
}) => {
|
||||
await visitPage(page, '/product');
|
||||
await expectShell(page, 'One operating model for change history, drift visibility, and review readiness.');
|
||||
await expectPrimaryNavigation(page);
|
||||
await expectFooterLinks(page);
|
||||
await expect(page.getByRole('heading', { name: 'Connected governance model' }).first()).toBeVisible();
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'See audience fit' }).first()).toBeVisible();
|
||||
await expect(
|
||||
page
|
||||
.getByRole('main')
|
||||
.getByRole('link', { name: 'Talk through your current operating model' })
|
||||
.first(),
|
||||
).toBeVisible();
|
||||
});
|
||||
45
apps/website/tests/smoke/smoke-helpers.ts
Normal file
45
apps/website/tests/smoke/smoke-helpers.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
export const primaryNavigationLabels = [
|
||||
'Product',
|
||||
'Solutions',
|
||||
'Security & Trust',
|
||||
'Integrations',
|
||||
'Contact',
|
||||
] as const;
|
||||
|
||||
export const footerLabels = ['Legal', 'Privacy', 'Terms', 'Contact / Demo'] as const;
|
||||
|
||||
export async function visitPage(page: Page, path: string): Promise<void> {
|
||||
await page.goto(path);
|
||||
await expect(page).toHaveURL(new RegExp(path === '/' ? '/?$' : `${path}$`));
|
||||
}
|
||||
|
||||
export async function expectShell(page: Page, heading: string | RegExp): Promise<void> {
|
||||
await expect(page.getByRole('banner')).toBeVisible();
|
||||
await expect(page.getByRole('main')).toBeVisible();
|
||||
await expect(page.getByRole('contentinfo')).toBeVisible();
|
||||
await expect(page.getByRole('heading', { level: 1, name: heading })).toBeVisible();
|
||||
}
|
||||
|
||||
export async function expectPrimaryNavigation(page: Page): Promise<void> {
|
||||
const header = page.getByRole('banner');
|
||||
|
||||
for (const label of primaryNavigationLabels) {
|
||||
await expect(header.getByRole('link', { name: label })).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export async function expectFooterLinks(page: Page): Promise<void> {
|
||||
for (const label of footerLabels) {
|
||||
await expect(page.getByRole('contentinfo').getByRole('link', { name: label })).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export async function openMobileNavigation(page: Page): Promise<void> {
|
||||
const menuTrigger = page.getByLabel('Open navigation menu');
|
||||
|
||||
if (await menuTrigger.isVisible()) {
|
||||
await menuTrigger.click();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import {
|
||||
expectFooterLinks,
|
||||
expectPrimaryNavigation,
|
||||
expectShell,
|
||||
visitPage,
|
||||
} from './smoke-helpers';
|
||||
|
||||
test('solutions separates MSP and enterprise fit clearly', async ({ page }) => {
|
||||
await visitPage(page, '/solutions');
|
||||
await expectShell(page, /MSP|enterprise/i);
|
||||
await expectPrimaryNavigation(page);
|
||||
await expectFooterLinks(page);
|
||||
await expect(page.getByRole('heading', { name: 'MSP operating model' })).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: 'Enterprise IT operating model' })).toBeVisible();
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'Review the ecosystem fit' }).first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('security and trust stays grounded in substantiated product posture', async ({ page }) => {
|
||||
await visitPage(page, '/security-trust');
|
||||
await expectShell(page, /trust posture|trust-first/i);
|
||||
await expectPrimaryNavigation(page);
|
||||
await expectFooterLinks(page);
|
||||
await expect(page.getByRole('heading', { name: 'Substantiated public posture' }).first()).toBeVisible();
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'Read the legal surface' }).first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('integrations shows real ecosystem direction without wishlist claims', async ({ page }) => {
|
||||
await visitPage(page, '/integrations');
|
||||
await expectShell(page, /ecosystem fit|integrations/i);
|
||||
await expectPrimaryNavigation(page);
|
||||
await expectFooterLinks(page);
|
||||
await expect(page.getByText('Microsoft Graph')).toBeVisible();
|
||||
await expect(page.getByText('Entra ID')).toBeVisible();
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'Plan the working session' }).first()).toBeVisible();
|
||||
});
|
||||
18
apps/website/tsconfig.json
Normal file
18
apps/website/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
".astro/types.d.ts",
|
||||
"**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@ -11,6 +11,11 @@ # Operator UX & Surface Standards
|
||||
|
||||
This document is normative for new operator-facing UI work and for major UI refactors.
|
||||
|
||||
It follows the constitution vocabulary for `Native Surface`, `Custom Surface`,
|
||||
`Shared Detail Micro-UI`, and shell/page/detail state ownership. This
|
||||
document complements the constitution and MUST NOT be used as a parallel
|
||||
rulebook that redefines those terms.
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
TenantPilot is not a generic admin UI. It is an enterprise operator product for managed Microsoft tenant governance, backup, restore, monitoring, drift detection, and review workflows.
|
||||
@ -484,6 +489,11 @@ ## 14. Adoption Rules
|
||||
- new specs that materially affect operator UX
|
||||
|
||||
Specs should explicitly reference this document where relevant.
|
||||
Operational authoring and review workflow lives in `.specify/templates/spec-template.md`,
|
||||
`.specify/templates/plan-template.md`, `.specify/templates/tasks-template.md`,
|
||||
`.specify/templates/checklist-template.md`, and `.specify/README.md`.
|
||||
This document remains the rule and reference layer, not a second checklist
|
||||
or close-out workflow.
|
||||
|
||||
Recommended spec fields:
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ importers:
|
||||
devDependencies:
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.0.0
|
||||
version: 4.2.2(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))
|
||||
version: 4.2.2(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))
|
||||
axios:
|
||||
specifier: ^1.11.0
|
||||
version: 1.14.0
|
||||
@ -27,7 +27,7 @@ importers:
|
||||
version: 0.45.2(pg@8.20.0)
|
||||
laravel-vite-plugin:
|
||||
specifier: ^2.0.0
|
||||
version: 2.1.0(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))
|
||||
version: 2.1.0(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))
|
||||
pg:
|
||||
specifier: ^8.16.3
|
||||
version: 8.20.0
|
||||
@ -39,13 +39,29 @@ importers:
|
||||
version: 4.2.2
|
||||
vite:
|
||||
specifier: ^7.0.7
|
||||
version: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
version: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
|
||||
apps/website:
|
||||
dependencies:
|
||||
astro:
|
||||
specifier: ^6.0.0
|
||||
version: 6.1.4(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(tsx@4.21.0)
|
||||
version: 6.1.4(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(tsx@4.21.0)(typescript@5.9.3)
|
||||
devDependencies:
|
||||
'@playwright/test':
|
||||
specifier: ^1.59.1
|
||||
version: 1.59.1
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.2.2
|
||||
version: 4.2.2(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))
|
||||
'@types/node':
|
||||
specifier: ^24.7.2
|
||||
version: 24.12.2
|
||||
tailwindcss:
|
||||
specifier: ^4.2.2
|
||||
version: 4.2.2
|
||||
typescript:
|
||||
specifier: ^5.9.3
|
||||
version: 5.9.3
|
||||
|
||||
packages:
|
||||
|
||||
@ -723,6 +739,11 @@ packages:
|
||||
'@oslojs/encoding@1.1.0':
|
||||
resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==}
|
||||
|
||||
'@playwright/test@1.59.1':
|
||||
resolution: {integrity: sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
'@rollup/pluginutils@5.3.0':
|
||||
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@ -1013,6 +1034,9 @@ packages:
|
||||
'@types/nlcst@2.0.3':
|
||||
resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==}
|
||||
|
||||
'@types/node@24.12.2':
|
||||
resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==}
|
||||
|
||||
'@types/unist@3.0.3':
|
||||
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
||||
|
||||
@ -2140,6 +2164,11 @@ packages:
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
ufo@1.6.3:
|
||||
resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
|
||||
|
||||
@ -2149,6 +2178,9 @@ packages:
|
||||
uncrypto@0.1.3:
|
||||
resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
|
||||
|
||||
undici-types@7.16.0:
|
||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||
|
||||
unified@11.0.5:
|
||||
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
||||
|
||||
@ -2784,6 +2816,10 @@ snapshots:
|
||||
|
||||
'@oslojs/encoding@1.1.0': {}
|
||||
|
||||
'@playwright/test@1.59.1':
|
||||
dependencies:
|
||||
playwright: 1.59.1
|
||||
|
||||
'@rollup/pluginutils@5.3.0(rollup@4.60.1)':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
@ -2968,12 +3004,12 @@ snapshots:
|
||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.2
|
||||
'@tailwindcss/oxide-win32-x64-msvc': 4.2.2
|
||||
|
||||
'@tailwindcss/vite@4.2.2(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))':
|
||||
'@tailwindcss/vite@4.2.2(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))':
|
||||
dependencies:
|
||||
'@tailwindcss/node': 4.2.2
|
||||
'@tailwindcss/oxide': 4.2.2
|
||||
tailwindcss: 4.2.2
|
||||
vite: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
|
||||
'@types/debug@4.1.13':
|
||||
dependencies:
|
||||
@ -2995,6 +3031,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
'@types/node@24.12.2':
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
|
||||
'@types/unist@3.0.3': {}
|
||||
|
||||
'@ungap/structured-clone@1.3.0': {}
|
||||
@ -3016,7 +3056,7 @@ snapshots:
|
||||
|
||||
array-iterate@2.0.1: {}
|
||||
|
||||
astro@6.1.4(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(tsx@4.21.0):
|
||||
astro@6.1.4(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(tsx@4.21.0)(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 3.0.1
|
||||
'@astrojs/internal-helpers': 0.8.0
|
||||
@ -3062,14 +3102,14 @@ snapshots:
|
||||
tinyclip: 0.1.12
|
||||
tinyexec: 1.1.1
|
||||
tinyglobby: 0.2.16
|
||||
tsconfck: 3.1.6
|
||||
tsconfck: 3.1.6(typescript@5.9.3)
|
||||
ultrahtml: 1.6.0
|
||||
unifont: 0.7.4
|
||||
unist-util-visit: 5.1.0
|
||||
unstorage: 1.17.5
|
||||
vfile: 6.0.3
|
||||
vite: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
vitefu: 1.1.3(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))
|
||||
vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
vitefu: 1.1.3(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))
|
||||
xxhash-wasm: 1.1.0
|
||||
yargs-parser: 22.0.0
|
||||
zod: 4.3.6
|
||||
@ -3614,10 +3654,10 @@ snapshots:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
laravel-vite-plugin@2.1.0(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)):
|
||||
laravel-vite-plugin@2.1.0(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)):
|
||||
dependencies:
|
||||
picocolors: 1.1.1
|
||||
vite: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
vite-plugin-full-reload: 1.2.0
|
||||
|
||||
lightningcss-android-arm64@1.32.0:
|
||||
@ -4411,7 +4451,9 @@ snapshots:
|
||||
|
||||
trough@2.2.0: {}
|
||||
|
||||
tsconfck@3.1.6: {}
|
||||
tsconfck@3.1.6(typescript@5.9.3):
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
@ -4422,12 +4464,16 @@ snapshots:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
ufo@1.6.3: {}
|
||||
|
||||
ultrahtml@1.6.0: {}
|
||||
|
||||
uncrypto@0.1.3: {}
|
||||
|
||||
undici-types@7.16.0: {}
|
||||
|
||||
unified@11.0.5:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
@ -4517,7 +4563,7 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
picomatch: 2.3.2
|
||||
|
||||
vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0):
|
||||
vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0):
|
||||
dependencies:
|
||||
esbuild: 0.27.7
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
@ -4526,14 +4572,15 @@ snapshots:
|
||||
rollup: 4.60.1
|
||||
tinyglobby: 0.2.16
|
||||
optionalDependencies:
|
||||
'@types/node': 24.12.2
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.6.1
|
||||
lightningcss: 1.32.0
|
||||
tsx: 4.21.0
|
||||
|
||||
vitefu@1.1.3(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)):
|
||||
vitefu@1.1.3(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)):
|
||||
optionalDependencies:
|
||||
vite: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
|
||||
web-namespaces@2.0.1: {}
|
||||
|
||||
|
||||
@ -1,175 +0,0 @@
|
||||
# Feature Specification: Dashboard Recovery Posture Honesty
|
||||
|
||||
**Feature Branch**: `[001-dashboard-recovery-honesty]`
|
||||
**Created**: 2026-04-08
|
||||
**Status**: Draft
|
||||
**Input**: User description: "Spec 184 — Dashboard Recovery Posture Honesty"
|
||||
|
||||
## Spec Scope Fields *(mandatory)*
|
||||
|
||||
- **Scope**: tenant
|
||||
- **Primary Routes**: `/admin/t/{tenant}`, `/admin/t/{tenant}/restore-runs`, `/admin/t/{tenant}/restore-runs/{record}`, `/admin/t/{tenant}/backup-sets`, `/admin/t/{tenant}/backup-sets/{record}`
|
||||
- **Data Ownership**: Tenant-owned `BackupSet`, `RestoreRun`, and linked `OperationRun` outcome context are read within the active workspace and tenant scope to derive a more honest overview statement. No new persisted recovery-confidence state is introduced.
|
||||
- **RBAC**: Workspace plus tenant membership remains required on every affected surface. Members who can open the tenant dashboard must see honest summary boundaries even when they cannot start or manage restore runs. Existing restore-run creation and mutation actions remain under current restore permissions. Non-members continue to receive deny-as-not-found semantics.
|
||||
|
||||
## UI/UX Surface Classification *(mandatory when operator-facing surfaces are changed)*
|
||||
|
||||
| Surface | Surface Type | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| Tenant dashboard KPI strip | Dashboard / stats overview | Explicit stat click per signal | forbidden | Supporting text inside the stat description | none | `/admin/t/{tenant}` | Signal-specific drill-through to `/admin/t/{tenant}/restore-runs` or `/admin/t/{tenant}/restore-runs/{record}` | Workspace context plus tenant context | Dashboard KPIs / Backup posture | Backup health is separate from restore evidence | existing widget pattern |
|
||||
| Needs Attention / Healthy Checks panel | Dashboard / attention summary | Explicit card CTA per attention item; healthy state is read-only | forbidden | Card CTA and helper copy only | none | `/admin/t/{tenant}` | `/admin/t/{tenant}/restore-runs`, `/admin/t/{tenant}/restore-runs/{record}` | Workspace context plus tenant context | Needs attention / Healthy checks | Unknown and weakened recovery confidence are visible before drilldown | existing widget pattern |
|
||||
| Restore runs page | CRUD / list-first resource | Full-row click to restore-run detail | required | Existing header action plus More menu | Existing More and bulk More groups | `/admin/t/{tenant}/restore-runs` | `/admin/t/{tenant}/restore-runs/{record}` | Tenant context plus restore-run identity | Restore runs / Restore run | Recent restore outcome and follow-up reason confirm the overview claim | none |
|
||||
|
||||
## Operator Surface Contract *(mandatory when operator-facing surfaces are changed)*
|
||||
|
||||
| Surface | Primary Persona | Surface Type | Primary Operator Question | Default-visible Information | Diagnostics-only Information | Status Dimensions Used | Mutation Scope | Primary Actions | Dangerous Actions |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| Tenant dashboard KPI strip | Tenant operator | Dashboard summary | Do healthy backups also have supporting restore evidence, or is that still unknown? | Backup posture, recovery-confidence qualifier, visible claim boundary, next step | Per-run causes, raw backup metadata, deeper restore evidence | backup health, recovery evidence availability, recent restore attention | None; read-only summary | Open restore history, open supporting backup context when backup health itself needs follow-up | none |
|
||||
| Needs Attention / Healthy Checks panel | Tenant operator | Dashboard attention and healthy-boundary surface | What recovery-confidence issue needs action now, and why? | No restore history, weakened recent restore history, boundary copy, concrete next action | Full restore results, preview or check details, low-level run metadata | backup health, recovery evidence availability, restore result attention, recency | None; read-only summary | Open restore history, open latest problematic restore run | none |
|
||||
| Restore runs page | Tenant operator | List and detail | Which restore runs explain the dashboard signal? | Recent restore status, result-attention reason, completed timing, related backup context | Assignment-level failures, preview detail, low-level result payloads | execution lifecycle, result attention, follow-up state | Existing restore-run maintenance actions only | Inspect restore run, create restore run | Existing rerun, archive, restore archived, and force-delete actions |
|
||||
|
||||
## Proportionality Review *(mandatory when structural complexity is introduced)*
|
||||
|
||||
- **New source of truth?**: no
|
||||
- **New persisted entity/table/artifact?**: no
|
||||
- **New abstraction?**: no
|
||||
- **New enum/state/reason family?**: no
|
||||
- **New cross-domain UI framework/taxonomy?**: no
|
||||
- **Current operator problem**: A tenant dashboard can currently look calm or healthy even when restore history is absent or recent restore results weaken confidence, so operators can overread backup health as recovery posture.
|
||||
- **Existing structure is insufficient because**: Backup health, restore history, and restore result attention already exist as separate truths, but the summary surfaces do not yet combine them with an honest claim boundary. Operators must manually cross-check multiple pages to avoid an overclaim.
|
||||
- **Narrowest correct implementation**: Derive a small set of overview honesty signals from existing backup health assessment, restore history presence, and per-run restore result attention, then show them on the existing dashboard widgets and existing restore-run drilldowns.
|
||||
- **Ownership cost**: Additional widget copy, narrow derived-summary logic, and focused feature plus RBAC regression tests that keep overview language and drilldown continuity aligned.
|
||||
- **Alternative intentionally rejected**: A new recovery-confidence score, enum, page, or persisted posture state was rejected because it would introduce new truth and new ownership cost before the current overview surfaces tell the existing truth accurately.
|
||||
- **Release truth**: current-release truth hardening
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - See Unvalidated Recovery Confidence Early (Priority: P1)
|
||||
|
||||
A tenant operator opens the tenant dashboard and needs to know within seconds whether healthy-looking backups are backed by any relevant restore evidence or whether recovery confidence is still unvalidated.
|
||||
|
||||
**Why this priority**: This is the highest-risk trust gap. If the first overview screen quietly converts healthy backups into a healthy recovery impression, later detail truth arrives too late.
|
||||
|
||||
**Independent Test**: Can be fully tested by rendering the tenant dashboard with healthy backup fixtures and no relevant restore history, then verifying that the overview shows an explicit unvalidated or unknown recovery-confidence signal instead of an all-clear.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a tenant has healthy backup posture and no relevant restore history, **When** the operator opens the tenant dashboard, **Then** the summary shows healthy backups plus an explicit unvalidated or unknown recovery-confidence message and a next action.
|
||||
2. **Given** the same tenant has no other attention items, **When** the healthy-check state renders, **Then** the widget does not show an unqualified all-good message and instead keeps the recovery-confidence boundary visible.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - Escalate Weak Restore History on Overview (Priority: P2)
|
||||
|
||||
A tenant operator reviewing the dashboard needs recent failed, partial, or follow-up restore results to affect the overview immediately instead of hiding inside restore history details.
|
||||
|
||||
**Why this priority**: Weak restore history is evidence that directly changes how much trust the operator should place in recovery posture. It cannot remain a drilldown-only fact.
|
||||
|
||||
**Independent Test**: Can be fully tested by rendering overview surfaces with recent failed, partial, and follow-up restore fixtures and verifying that each case creates a visible confidence-related attention signal with matching drilldown behavior.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a tenant has healthy backups but a recent failed or partial restore run, **When** the operator opens the dashboard, **Then** Needs Attention shows a recovery-confidence issue that links to restore history explaining the same failure state.
|
||||
2. **Given** a tenant has a recent restore run that completed with follow-up required, **When** the operator opens the dashboard, **Then** the overview shows weakened confidence rather than a neutral or healthy-only message.
|
||||
3. **Given** recent restore history exists without a current confidence-weakening attention state, **When** the operator opens the dashboard, **Then** the overview may say that no recent restore issues are visible but does not claim that recovery is proven.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - Preserve Honest Drilldowns and RBAC Boundaries (Priority: P3)
|
||||
|
||||
A tenant operator or read-only member needs the dashboard signal and the destination surface to tell the same story, while RBAC limits must never make the summary look stronger than the accessible evidence.
|
||||
|
||||
**Why this priority**: Overview honesty fails if the next click contradicts the dashboard or if authorization gaps hide weakness by omission.
|
||||
|
||||
**Independent Test**: Can be fully tested by opening overview signals as different tenant members, verifying that the linked restore-history surface confirms the same reason, and ensuring restricted users still see cautious summary language.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** the dashboard says recovery confidence is unvalidated because no relevant restore history exists, **When** the operator follows the dashboard action, **Then** the destination surface confirms that the tenant lacks relevant restore history.
|
||||
2. **Given** the dashboard says recovery confidence is weakened by a recent problematic restore, **When** the operator follows the dashboard action, **Then** the destination surface confirms the same failed, partial, or follow-up reason.
|
||||
3. **Given** a tenant member can see the dashboard but cannot open deeper restore evidence, **When** the dashboard renders, **Then** the summary remains cautious and truthful and does not replace missing evidence with a stronger claim.
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- A tenant has only draft, preview-only, or dry-run restore history; the overview treats recovery confidence as unvalidated rather than positive.
|
||||
- A tenant has both an older successful restore and a more recent failed or follow-up restore; the weakened signal takes precedence on the summary surface.
|
||||
- A summary signal points to a restore run that is no longer directly openable; the drilldown falls back to tenant-scoped restore history rather than a dead end.
|
||||
- A user can see the dashboard but lacks permission to inspect restore runs; the summary still states unknown or weakened confidence without suggesting that everything is healthy.
|
||||
- Healthy backup posture and backup-automation follow-up can coexist with unvalidated recovery confidence; the overview must not let one healthy-sounding statement erase the other caution.
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
This feature introduces no new Microsoft Graph calls, no new background work, no new `OperationRun`, and no new persistence. It is a read-first truth-hardening slice that makes existing backup and restore evidence visible more honestly on tenant overview surfaces.
|
||||
|
||||
Authorization remains in the tenant/admin plane under `/admin/t/{tenant}/...`. Non-members must continue to receive 404 responses. Established members missing deeper restore capabilities must continue to receive 403 on execution paths, but summary visibility must not depend on restore-mutation rights.
|
||||
|
||||
This slice reuses existing Filament dashboard widgets, stat descriptions, attention cards, and existing restore-run resource surfaces. No new local badge framework, page-local status language, or extra action surface is introduced. UI-FIL-001 is satisfied by continuing to use existing Filament widget primitives and shared status language. UX-001 create, edit, and detail-form rules are not materially changed; the dashboard keeps its existing layout, and the restore-run resource keeps its existing list-and-view contract.
|
||||
|
||||
The affected Filament surfaces keep exactly one primary inspect or open model, add no redundant View actions, and introduce no new destructive actions. Existing destructive restore-run actions continue to follow the current placement and confirmation rules. Action Surface Contract expectations therefore remain satisfied.
|
||||
|
||||
Existing per-run restore result attention remains the authoritative signal for restore outcome quality. This feature may summarize or elevate that truth, but it must not duplicate it with a second scoring or status system.
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-184-001**: The system MUST present tenant backup health and tenant recovery-confidence evidence as separate truths on tenant dashboard summary surfaces.
|
||||
- **FR-184-002**: When backup health is healthy but no relevant restore history exists, the system MUST display an explicit unknown or unvalidated recovery-confidence state and MUST NOT present an all-clear summary.
|
||||
- **FR-184-003**: When the system cannot determine recovery confidence from the available restore history, the system MUST say that limitation directly instead of inferring a positive recovery claim from backup health alone.
|
||||
- **FR-184-004**: Needs Attention or the healthy-boundary surface MUST surface absence of restore history as an overview-relevant condition with a clear next action.
|
||||
- **FR-184-005**: Recent restore history with `failed`, `partial`, `completed_with_follow_up`, or an equivalent confidence-weakening attention state MUST appear on overview surfaces as a recovery-confidence issue.
|
||||
- **FR-184-006**: Overview surfaces MUST distinguish unknown or unvalidated confidence from weakened confidence and MUST NOT collapse both states into one ambiguous bucket.
|
||||
- **FR-184-007**: Any positive backup-health summary on the dashboard MUST show a visible claim boundary that healthy backups reflect backup inputs only and do not prove restore success.
|
||||
- **FR-184-008**: Healthy checks MUST NOT render an unqualified healthy or all-clear state when recovery confidence is unknown, weakened, or not evaluated.
|
||||
- **FR-184-009**: When recovery confidence is unknown or weakened, overview copy MUST explain what is missing or concerning, why that affects confidence, and what the operator should do next.
|
||||
- **FR-184-010**: Overview signals about missing restore history MUST drill into a tenant-scoped restore-history surface that confirms the absence or insufficiency of relevant restore evidence.
|
||||
- **FR-184-011**: Overview signals about weakened restore history MUST drill into a tenant-scoped restore-history surface or restore-run detail that confirms the same failed, partial, or follow-up reason shown on the summary surface.
|
||||
- **FR-184-012**: The feature MUST reuse existing per-run restore result attention as the authoritative quality signal for restore outcomes and MUST NOT introduce a parallel positive-scoring or reason system.
|
||||
- **FR-184-013**: The feature MUST NOT introduce a new state or message that claims recovery is proven, guaranteed, or strongly confirmed beyond the evidence the current system already has.
|
||||
- **FR-184-014**: RBAC limits on restore history visibility MUST NOT cause summary surfaces to make stronger recovery claims than the visible evidence supports; when detailed restore evidence cannot be opened, the summary must remain cautious and truthful.
|
||||
- **FR-184-015**: Tenant-linked summaries shown outside the tenant dashboard, if they reuse this posture signal, MUST preserve the same meaning for unknown, weakened, and backup-only-positive states.
|
||||
- **FR-184-016**: The feature MUST derive its summary state from existing tenant backup health, restore history, and restore result attention records and MUST NOT add a new persisted recovery-confidence field, table, or scoring artifact.
|
||||
- **FR-184-017**: When recent restore history exists without a current confidence-weakening attention state, overview surfaces MAY state that no recent restore issues are visible, but MUST stop short of claiming recovery proof.
|
||||
|
||||
## Assumptions
|
||||
|
||||
- Relevant restore history means tenant-scoped restore runs that have reached an executed result state or another existing result-attention state that the current system can classify. Draft-only, preview-only, or dry-run-only history does not count as proven recovery evidence.
|
||||
- Existing restore history surfaces already show enough result detail to confirm failed, partial, and follow-up reasons once the operator drills down from the overview.
|
||||
- Workspace-level surfaces that later reuse this posture language should consume the same tenant-level semantics rather than creating a separate recovery-confidence vocabulary.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Existing tenant dashboard surfaces remain the operator entry point for this slice.
|
||||
- Existing `TenantBackupHealthAssessment` and `TenantBackupHealthResolver` remain the source of backup-input truth.
|
||||
- Existing `RestoreRun` history surfaces and `RestoreSafetyResolver::resultAttentionForRun(...)` remain the source of restore-outcome truth.
|
||||
- Existing RBAC helper-text and disabled-link patterns remain the fallback behavior when the operator cannot open deeper restore evidence.
|
||||
|
||||
## Out of Scope and Follow-up
|
||||
|
||||
- No new recovery-confidence engine, score, enum, or dedicated posture page.
|
||||
- No automatic restore validation, scheduled restore probes, or restore execution changes.
|
||||
- No new backup-health rules, restore-result-attention taxonomy changes, or restore-safety model redesign.
|
||||
- No new claim that a tenant is recovery-proven.
|
||||
- Reasonable follow-up work includes broader workspace-level recovery rollups after tenant-level overview honesty is stable.
|
||||
|
||||
## UI Action Matrix *(mandatory when Filament is changed)*
|
||||
|
||||
| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| Tenant dashboard summary widgets | `app/Filament/Pages/TenantDashboard.php`, `app/Filament/Widgets/Dashboard/DashboardKpis.php`, `app/Filament/Widgets/Dashboard/NeedsAttention.php` | none added | Explicit stat and card CTA only; no row click | none | n/a | none | n/a | n/a | no new audit event | Action Surface Contract stays satisfied because the dashboard remains read-only. UI-FIL-001 stays satisfied through existing Filament widget primitives. UX-001 create and edit form rules are not applicable to this dashboard slice. |
|
||||
| RestoreRunResource list and detail | `app/Filament/Resources/RestoreRunResource.php`, `app/Filament/Resources/RestoreRunResource/Pages/ListRestoreRuns.php`, `app/Filament/Resources/RestoreRunResource/Pages/ViewRestoreRun.php` | Existing `New restore run` action remains | `recordUrl()` clickable row to restore-run detail | Existing More-menu maintenance actions remain unchanged | Existing grouped bulk actions remain unchanged | Existing `New restore run` empty-state CTA remains | none added | Existing restore-run create flow remains unchanged | existing restore-run mutation audit behavior only | This spec reuses restore-run list and detail as canonical drilldowns and adds no new destructive action or placement exception. |
|
||||
|
||||
## Key Entities *(include if feature involves data)*
|
||||
|
||||
- **Backup health assessment**: Tenant-level summary of backup freshness and input health that is useful but not sufficient to prove recovery success.
|
||||
- **Restore history**: Tenant-scoped record of restore runs whose presence, absence, and recent outcomes affect how strongly the product can speak about recovery confidence.
|
||||
- **Restore result attention**: Per-run classification that distinguishes completed, failed, partial, follow-up, and not-executed outcome states that matter for operator trust.
|
||||
- **Recovery posture summary**: Non-persisted dashboard statement that combines backup health, restore history presence, and restore-result attention without becoming a new score or stored state.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: In acceptance testing, operators can identify within 10 seconds whether a tenant has healthy backups plus unvalidated or weakened recovery evidence from `/admin/t/{tenant}` without opening raw details.
|
||||
- **SC-002**: In 100% of tested tenants with no relevant restore history, the dashboard or healthy-boundary surface shows an explicit unvalidated or unknown recovery-confidence signal and never shows a healthy-only all-clear.
|
||||
- **SC-003**: In 100% of tested tenants with recent failed, partial, or follow-up restore runs, the overview shows a confidence-related attention item with a drilldown that confirms the same reason.
|
||||
- **SC-004**: In 100% of tested positive backup-health scenarios, summary-level copy includes the claim boundary that healthy backups do not prove restore success.
|
||||
- **SC-005**: In 100% of tested RBAC-restricted scenarios, summary surfaces remain cautious and truthful even when the user cannot open deeper restore evidence pages.
|
||||
@ -1,126 +0,0 @@
|
||||
# Implementation Plan: Finding Risk Acceptance Lifecycle
|
||||
|
||||
**Branch**: `001-finding-risk-acceptance` | **Date**: 2026-03-19 | **Spec**: [/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/001-finding-risk-acceptance/spec.md](/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/001-finding-risk-acceptance/spec.md)
|
||||
**Input**: Feature specification from `/specs/001-finding-risk-acceptance/spec.md`
|
||||
|
||||
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.
|
||||
|
||||
## Summary
|
||||
|
||||
Introduce a first-class tenant-owned Finding Exception domain that governs formal risk acceptance for findings instead of relying on a bare `risk_accepted` status and freeform reason field. The implementation adds dedicated exception and exception-decision records, tenant-scoped request and detail surfaces, a canonical workspace approval queue, centralized validity semantics, audit coverage for every lifecycle mutation, and explicit downstream contracts so evidence and reporting flows can distinguish valid governed exceptions from expired, revoked, rejected, or missing ones.
|
||||
|
||||
The implementation keeps Findings as the system of record for the underlying issue, uses the existing `FindingWorkflowService` as the only path that can transition a finding into or out of `risk_accepted`, stores governance history in append-only decision records, and uses DB-backed tenant/workspace queries rather than a new `OperationRun` workflow for normal approval actions.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: PHP 8.4.15
|
||||
**Primary Dependencies**: Laravel 12, Filament v5, Livewire v4, Pest v4, existing Finding, AuditLog, EvidenceSnapshot, CapabilityResolver, WorkspaceCapabilityResolver, and UiEnforcement patterns
|
||||
**Storage**: PostgreSQL with new tenant-owned exception tables and JSONB-backed supporting metadata
|
||||
**Testing**: Pest feature tests, Pest unit tests, and Livewire/Filament component tests
|
||||
**Target Platform**: Laravel Sail web application on PostgreSQL
|
||||
**Project Type**: Web application monolith
|
||||
**Performance Goals**: Exception request, approval, rejection, renewal, and revocation remain synchronous DB-backed actions under 2 seconds; tenant and canonical exception lists remain DB-only at render time; expiring queue filters remain index-backed
|
||||
**Constraints**: No Microsoft Graph calls; no new public API; one current valid active exception per finding at a time; approval history must remain append-only; normal workflow stays outside `OperationRun`; status-like UI uses centralized badge semantics
|
||||
**Scale/Scope**: First rollout covers finding-specific exceptions only, tenant detail plus workspace approval queue, linked evidence references, validity-state evaluation, and downstream reuse by evidence/reporting consumers
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
- **Pre-Phase-0 Gate: PASS**
|
||||
- Inventory-first: PASS. The feature governs findings and linked evidence already present in the product; it does not recollect or redefine source inventory.
|
||||
- Read/write separation: PASS. Exception request, approval, rejection, renewal, and revocation are explicit governance writes with confirmation, audit coverage, and focused tests.
|
||||
- Graph contract path: PASS. No Graph calls are introduced.
|
||||
- Deterministic capabilities: PASS. New capabilities are added to the canonical registry and role maps and tested through existing capability resolver patterns.
|
||||
- RBAC-UX / workspace / tenant isolation: PASS. Tenant exception records stay tenant-owned; the canonical workspace queue is query-only and entitlement-filtered; non-members remain 404 and in-scope capability denials remain 403.
|
||||
- Global search: PASS. The first rollout does not require global-search exposure for exception records.
|
||||
- Run observability: PASS with explicit exemption. Normal exception decisions are DB-only and expected to complete under 2 seconds, so they intentionally skip `OperationRun` and rely on audit history and surface state changes. No remote or long-running work is introduced.
|
||||
- Ops-UX 3-surface feedback: PASS by non-applicability. No new `OperationRun`-driven operator workflow is introduced in v1.
|
||||
- Ops-UX lifecycle / summary counts / system runs: PASS by non-applicability for the core decision paths.
|
||||
- Data minimization: PASS. Exception records store bounded justification, structured evidence references, and sanitized audit context; no raw payloads or secrets are persisted.
|
||||
- BADGE-001: PASS. New exception-state and validity-state badges are introduced via centralized badge domain entries and covered by tests.
|
||||
- UI-NAMING-001: PASS. Operator-facing vocabulary remains `Request exception`, `Approve exception`, `Reject exception`, `Renew exception`, and `Revoke exception` with `risk acceptance` used for the governed outcome.
|
||||
- Filament UI Action Surface Contract: PASS. Tenant finding detail, tenant exception register, canonical approval queue, and exception detail all use explicit inspection affordances, grouped actions, and confirmed destructive-like mutations.
|
||||
- Filament UI UX-001: PASS. Detail surfaces are inspection-first Infolists; list surfaces expose search, sort, and filters; exception request and renewal use structured sections in modals or dedicated forms.
|
||||
|
||||
**Post-Phase-1 Re-check: PASS**
|
||||
- The design keeps Findings as the underlying domain record, adds a tenant-owned governance layer without cross-tenant duplication, routes all status mutations through the existing workflow service, avoids unnecessary `OperationRun` usage, and preserves audit-first history for every decision path.
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/001-finding-risk-acceptance/
|
||||
├── plan.md # This file (/speckit.plan command output)
|
||||
├── research.md # Phase 0 output (/speckit.plan command)
|
||||
├── data-model.md # Phase 1 output (/speckit.plan command)
|
||||
├── quickstart.md # Phase 1 output (/speckit.plan command)
|
||||
├── contracts/ # Phase 1 output (/speckit.plan command)
|
||||
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
```text
|
||||
app/
|
||||
├── Filament/
|
||||
│ ├── Pages/
|
||||
│ │ └── Monitoring/
|
||||
│ └── Resources/
|
||||
├── Models/
|
||||
├── Policies/
|
||||
├── Services/
|
||||
│ ├── Audit/
|
||||
│ ├── Auth/
|
||||
│ ├── Evidence/
|
||||
│ └── Findings/
|
||||
└── Support/
|
||||
├── Audit/
|
||||
├── Auth/
|
||||
├── Badges/
|
||||
└── Rbac/
|
||||
|
||||
database/
|
||||
└── migrations/
|
||||
|
||||
tests/
|
||||
├── Feature/
|
||||
│ ├── Findings/
|
||||
│ ├── Monitoring/
|
||||
│ └── Guards/
|
||||
└── Unit/
|
||||
├── Findings/
|
||||
└── Support/
|
||||
```
|
||||
|
||||
**Structure Decision**: Keep the existing Laravel monolith structure. Add new exception models and decision-history tables under `app/Models`, lifecycle orchestration under `app/Services/Findings`, authorization under `app/Policies`, and tenant/canonical Filament surfaces under `app/Filament`. Persist schema in `database/migrations` and cover behavior with focused Pest feature/unit tests in existing Findings, Monitoring, and guard suites.
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
> **Fill ONLY if Constitution Check has violations that must be justified**
|
||||
|
||||
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||
|-----------|------------|-------------------------------------|
|
||||
|
||||
## Phase 0 — Research Output
|
||||
|
||||
- [research.md](/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/001-finding-risk-acceptance/research.md)
|
||||
|
||||
## Phase 1 — Design Output
|
||||
|
||||
- [data-model.md](/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/001-finding-risk-acceptance/data-model.md)
|
||||
- [quickstart.md](/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/001-finding-risk-acceptance/quickstart.md)
|
||||
- [contracts/finding-risk-acceptance.openapi.yaml](/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/001-finding-risk-acceptance/contracts/finding-risk-acceptance.openapi.yaml)
|
||||
|
||||
## Phase 2 — Implementation Planning
|
||||
|
||||
`tasks.md` should cover:
|
||||
|
||||
1. Schema creation for `finding_exceptions` and `finding_exception_decisions` with tenant/workspace ownership constraints, validity indexes, and evidence-reference metadata.
|
||||
2. Capability registry and role-map updates for `finding_exception.view`, `finding_exception.manage`, and `finding_exception.approve` plus authorization policies for tenant and canonical views.
|
||||
3. Service-layer orchestration that routes all accepted-risk status mutations through a new exception lifecycle service plus the existing `FindingWorkflowService`.
|
||||
4. Filament tenant finding-detail, tenant exception register, canonical approval queue, and exception detail surfaces aligned with Action Surface and UX-001 rules.
|
||||
5. Audit-log integration, badge-domain additions, and canonical related-navigation support.
|
||||
6. Downstream validity-resolution hooks for evidence and reporting consumers that must distinguish valid governed exceptions from expired, revoked, rejected, or missing ones.
|
||||
7. Focused Pest coverage for positive and negative authorization, invalid transitions, renewal/revocation history, wrong-tenant behavior, and canonical queue filtering.
|
||||
| [e.g., 4th project] | [current need] | [why 3 projects insufficient] |
|
||||
| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] |
|
||||
@ -1,203 +0,0 @@
|
||||
# Feature Specification: Finding Risk Acceptance Lifecycle
|
||||
|
||||
**Feature Branch**: `001-finding-risk-acceptance`
|
||||
**Created**: 2026-03-19
|
||||
**Status**: Draft
|
||||
**Input**: User description: "Create a formal exception and risk acceptance workflow for findings with approval, expiry, renewal, audit trail, and evidence linkage."
|
||||
|
||||
## Spec Scope Fields *(mandatory)*
|
||||
|
||||
- **Scope**: tenant + canonical-view
|
||||
- **Primary Routes**:
|
||||
- `/admin/t/{tenant}/findings/{finding}` as the tenant-context finding inspection surface where operators can review and initiate risk-acceptance requests
|
||||
- `/admin/t/{tenant}/exceptions` as the tenant-scoped exception register for active, pending, expiring, expired, rejected, and revoked finding exceptions
|
||||
- `/admin/exceptions` as the canonical workspace review and governance queue for authorized approvers and auditors
|
||||
- Existing evidence and audit destinations remain drill-down targets from exception detail when the operator is entitled to inspect them
|
||||
- **Data Ownership**:
|
||||
- Tenant-owned: finding exception records, approval decisions, renewal decisions, expiry state, revocation state, and linked evidence references for one tenant's findings
|
||||
- Workspace-owned but tenant-filtered: canonical review queue state, approval workload filters, and workspace-level summaries for expiring or overdue exceptions without changing tenant ownership of the exception itself
|
||||
- Existing findings, evidence snapshots, review packs, and audit events remain separate systems of record and are referenced rather than duplicated
|
||||
- **RBAC**:
|
||||
- Workspace membership remains required for every exception workflow surface
|
||||
- Tenant entitlement remains required to inspect or mutate tenant-scoped exception records
|
||||
- `finding_exception.view` permits reviewing exception details within authorized scope
|
||||
- `finding_exception.manage` permits creating requests, renewing requests, attaching justification and evidence references, and revoking exceptions where policy allows
|
||||
- `finding_exception.approve` permits approving or rejecting requests and renewals within authorized scope
|
||||
- Non-members or users outside the relevant workspace or tenant scope remain deny-as-not-found, while in-scope members lacking the required capability remain forbidden
|
||||
|
||||
For canonical-view specs, the spec MUST define:
|
||||
|
||||
- **Default filter behavior when tenant-context is active**: When an operator navigates from a tenant finding into the shared exceptions queue, the canonical workspace view opens with that tenant prefiltered. The operator may clear or change the filter only within their authorized tenant set.
|
||||
- **Explicit entitlement checks preventing cross-tenant leakage**: Exception queries, counts, approver queues, filter options, related finding labels, and linked evidence references must be assembled only after workspace and tenant entitlement checks. Unauthorized users must not learn whether another tenant has pending, active, expiring, or expired exceptions.
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - Propose and approve a time-bounded risk acceptance (Priority: P1)
|
||||
|
||||
As a tenant manager, I want to request a formal risk acceptance for a finding and route it for approval, so that a risk decision becomes explicit, reviewable, and time-bounded instead of being hidden behind a status flag.
|
||||
|
||||
**Why this priority**: This is the core governance gap. Without a first-class request and approval flow, the product still cannot answer who accepted a risk, why, and until when.
|
||||
|
||||
**Independent Test**: Can be fully tested by creating a finding, submitting a risk-acceptance request with justification and review date, approving it as an authorized approver, and verifying that the finding becomes governed by a valid active exception.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a finding is open and no active exception exists, **When** an authorized operator submits a risk-acceptance request with justification, owner, and review deadline, **Then** the system creates a pending exception request linked to that finding.
|
||||
2. **Given** a pending exception request exists, **When** an authorized approver approves it, **Then** the exception becomes active with a recorded approver, decision time, and expiry date.
|
||||
3. **Given** a pending exception request exists, **When** an authorized approver rejects it, **Then** the request records the rejection outcome and reason without changing the finding into an accepted-risk state.
|
||||
4. **Given** a user lacks the relevant capability or tenant entitlement, **When** they attempt to create or approve an exception request, **Then** the server denies the action with the correct 404 or 403 behavior.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - See whether accepted risk is still valid (Priority: P1)
|
||||
|
||||
As an auditor or workspace approver, I want a clear register of pending, active, expiring, expired, rejected, and revoked exceptions, so that I can tell which accepted risks are still valid and which require action.
|
||||
|
||||
**Why this priority**: A risk-acceptance workflow is only governable if operators can review its current state without reconstructing history from comments and status changes.
|
||||
|
||||
**Independent Test**: Can be fully tested by creating exception records in several lifecycle states and verifying that tenant and canonical views expose the correct state, dates, owners, and next-action cues without leaking unauthorized tenant data.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a tenant has pending, active, and expired exceptions, **When** an authorized operator opens the tenant exception register, **Then** each exception clearly shows its lifecycle state, finding, owner, approver context, and review timing.
|
||||
2. **Given** an approver is responsible for multiple tenants, **When** they open the canonical exceptions queue, **Then** they can filter by tenant, state, and due timing without seeing unauthorized tenants.
|
||||
3. **Given** an active exception is nearing expiry, **When** an authorized operator inspects the register, **Then** the exception is visibly distinguished from long-valid exceptions.
|
||||
4. **Given** no exception matches the current filters, **When** the operator opens the register, **Then** the empty state explains that no governed exceptions match and offers exactly one clear next action.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - Renew or revoke an accepted risk with audit evidence (Priority: P2)
|
||||
|
||||
As a governance operator, I want to renew or revoke an existing accepted risk with a durable decision trail and linked evidence, so that exceptions stay current rather than becoming permanent silent waivers.
|
||||
|
||||
**Why this priority**: Time-bounded approval loses value if the product cannot handle renewal and revocation as first-class governance decisions.
|
||||
|
||||
**Independent Test**: Can be fully tested by renewing an active exception with new justification and evidence references, revoking another one, and verifying that lifecycle history, current validity, and audit trail remain intelligible.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** an active exception is approaching expiry, **When** an authorized operator submits a renewal request with updated justification and supporting evidence references, **Then** the system records a new renewal decision path without rewriting the earlier decision.
|
||||
2. **Given** a renewal request exists, **When** an authorized approver approves it, **Then** the active-validity window extends and the prior decision history remains visible.
|
||||
3. **Given** an active exception is no longer acceptable, **When** an authorized operator revokes it with a reason, **Then** the exception becomes revoked and no longer counts as valid risk acceptance.
|
||||
4. **Given** a linked evidence snapshot or supporting artifact later disappears from active views, **When** an operator reviews the exception history, **Then** the exception remains understandable from stored reference metadata.
|
||||
|
||||
---
|
||||
|
||||
### User Story 4 - Detect governance drift in accepted-risk findings (Priority: P2)
|
||||
|
||||
As a compliance-focused operator, I want the system to surface findings marked as accepted risk without a currently valid exception, so that governance drift is visible instead of silently undermining auditability.
|
||||
|
||||
**Why this priority**: The business risk is not just missing workflow. It is false confidence when a finding looks accepted even though its approval expired, was revoked, or never existed.
|
||||
|
||||
**Independent Test**: Can be fully tested by creating findings in accepted-risk status with valid, expired, revoked, and missing exception records and verifying that only truly valid exceptions count as accepted governance state.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a finding is marked as accepted risk and has a valid active exception, **When** the operator inspects it, **Then** the finding shows that the acceptance is governed and time-bounded.
|
||||
2. **Given** a finding is marked as accepted risk but the linked exception is expired, revoked, or absent, **When** the operator inspects it or opens the exception queue, **Then** the system surfaces it as a governance warning rather than a valid accepted risk.
|
||||
3. **Given** a downstream review or evidence workflow summarizes accepted risks, **When** it evaluates findings, **Then** only findings backed by a currently valid exception count as active risk acceptance.
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- A finding is resolved or closed while an exception request is still pending; the request must not silently convert into an active accepted risk without an explicit decision.
|
||||
- A finding remains in `risk_accepted` status after the governing exception expires or is revoked; the system must show that the risk state is no longer valid.
|
||||
- An operator attempts to renew an exception that is already expired; the renewal path must remain explicit and must not overwrite the expired decision history.
|
||||
- The same person requests and approves an exception; the system must either block self-approval in normal flow or record an explicit elevated-policy override when self-approval is allowed.
|
||||
- A finding reopens through detection recurrence while a previous exception exists; the system must make it clear whether the earlier exception still governs the re-opened risk or whether a fresh decision is required.
|
||||
- Evidence linked to an exception may be partial, stale, or later removed from active surfaces; the exception history must preserve enough reference context for review.
|
||||
- A workspace approver can review multiple tenants, but must not see queue counts, labels, or filter values for unauthorized tenants.
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
**Constitution alignment (required):** This feature introduces a new governance data model and new user-driven write behavior, but no new Microsoft Graph calls. Exception request, approval, renewal, rejection, expiry, and revocation are security-relevant DB-backed governance mutations and must be explicitly audited. The feature must define tenant isolation, approval safety, validity semantics, linked-evidence semantics, and tests for valid, expired, revoked, missing, and unauthorized paths. If scheduled reminder or expiry evaluation is introduced, it must describe how that work is observable and how it avoids cross-tenant leakage.
|
||||
|
||||
**Constitution alignment (OPS-UX):** The primary workflow is synchronous governance mutation and does not require a dedicated long-running `OperationRun` for request, approval, rejection, renewal, or revocation. These decisions must therefore be observable through audit history, surface state changes, and user notifications instead of an operation progress surface. If the product later adds scheduled reminder or expiry evaluation, that work may integrate with existing monitoring or alerting patterns, but the first release of this feature does not rely on a new operator-facing progress workflow.
|
||||
|
||||
**Constitution alignment (RBAC-UX):** This feature operates in the tenant/admin plane for tenant-scoped finding and exception surfaces and in the workspace-admin canonical view for the approval queue. Cross-plane access remains deny-as-not-found. Non-members or users outside workspace or tenant scope receive `404`. In-scope users lacking `finding_exception.view`, `finding_exception.manage`, or `finding_exception.approve` receive `403` according to the attempted action. Authorization must be enforced server-side for request creation, approval, rejection, renewal, revocation, and any canonical queue action. The canonical capability registry remains the only capability source. Destructive-like actions such as revoke and reject require confirmation.
|
||||
|
||||
**Constitution alignment (OPS-EX-AUTH-001):** Not applicable. No authentication handshake behavior is changed.
|
||||
|
||||
**Constitution alignment (BADGE-001):** Exception lifecycle state, risk-governance validity, and due-timing indicators are status-like values and must use centralized badge semantics rather than per-page color choices. Tests must cover all introduced states such as pending, active, expiring, expired, rejected, and revoked.
|
||||
|
||||
**Constitution alignment (UI-NAMING-001):** The target object is the finding exception. Operator-facing verbs are `Request exception`, `Approve exception`, `Reject exception`, `Renew exception`, and `Revoke exception`. The term `risk acceptance` describes the governance outcome, while `exception` names the governed record. The same vocabulary must be preserved across finding detail, exception register, approval queue, audit prose, and notifications. Implementation-first terms such as `waiver row`, `approval token`, or `state machine` must not become primary labels.
|
||||
|
||||
**Constitution alignment (Filament Action Surfaces):** This feature modifies tenant finding detail and introduces exception list and detail inspection surfaces plus approval actions. The Action Surface Contract is satisfied if request and review actions are explicit, destructive-like actions require confirmation, list inspection uses a canonical inspect affordance, and every mutation is authorization-gated and audited.
|
||||
|
||||
**Constitution alignment (UX-001 — Layout & Information Architecture):** Exception list screens must provide search, sort, and filters for state, tenant, owner, approver, and expiry timing. Exception detail must be an inspection surface using Infolist-style composition rather than a disabled edit form. Creation and renewal may use a structured modal or dedicated form surface, but must keep justification, owner, timing, and evidence references grouped inside sections. Empty states must include a specific title, explanation, and exactly one CTA.
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: The system MUST provide a first-class finding exception record that governs formal risk acceptance for a specific finding.
|
||||
- **FR-002**: A finding exception MUST capture at minimum the target finding, requester, accountable owner, requested justification, requested decision time, and the bounded validity window for accepted risk.
|
||||
- **FR-003**: The system MUST support an exception lifecycle that distinguishes at least pending, active, expiring, expired, rejected, revoked, and superseded or renewed states.
|
||||
- **FR-004**: An operator MUST be able to request risk acceptance for a finding without directly bypassing the approval lifecycle.
|
||||
- **FR-005**: The system MUST support explicit approval and explicit rejection of pending exception requests, with durable decision reason and actor history.
|
||||
- **FR-006**: The system MUST support renewal of an existing exception as a new governance decision that preserves earlier request and approval history.
|
||||
- **FR-007**: The system MUST support explicit revocation of an active exception, with recorded actor, time, and revocation reason.
|
||||
- **FR-008**: The system MUST treat a finding as having valid accepted risk only while a currently valid active exception exists for that finding.
|
||||
- **FR-009**: A finding in `risk_accepted` status without a currently valid exception MUST be surfaced as a governance warning rather than a fully governed accepted risk.
|
||||
- **FR-010**: The feature MUST define whether one finding may have multiple historical exception records over time, while ensuring that only one current exception can govern the finding for a given validity window.
|
||||
- **FR-011**: Exception requests and renewals MUST support structured supporting context, including freeform justification and one or more linked evidence references when available.
|
||||
- **FR-012**: Evidence references linked to an exception MUST remain intelligible even if the live evidence artifact later expires, is superseded, or becomes inaccessible from normal active views.
|
||||
- **FR-013**: The system MUST provide a tenant-scoped exception register that allows authorized operators to review current and historical exception records for that tenant.
|
||||
- **FR-014**: The system MUST provide a canonical workspace approval and governance queue that allows authorized viewers to review pending, expiring, expired, rejected, and revoked exceptions across entitled tenants.
|
||||
- **FR-015**: Tenant and canonical views MUST provide filters for lifecycle state, due timing, requester, owner, approver, and finding severity or type where relevant.
|
||||
- **FR-016**: The system MUST make upcoming expiry and already-expired exceptions clearly visible so that time-bounded risk acceptance does not silently lapse.
|
||||
- **FR-017**: The system MUST define reminder semantics for exceptions nearing expiry, including who needs visibility when action is required.
|
||||
- **FR-018**: All exception lifecycle mutations must be recorded in audit history with workspace scope, tenant scope, actor, target finding context, action, outcome, and readable supporting context.
|
||||
- **FR-019**: Exception audit records MUST be summary-first and MUST NOT store secrets, raw evidence payloads, or arbitrary oversized snapshots.
|
||||
- **FR-020**: The system MUST enforce 404 deny-as-not-found behavior for non-members and out-of-scope users, and 403 behavior for in-scope users lacking the required capability.
|
||||
- **FR-021**: The feature MUST define approval separation rules, including whether normal self-approval is blocked and how any exceptional override path is governed and auditable.
|
||||
- **FR-022**: The feature MUST preserve intelligible history when a finding later resolves, closes, reopens, or changes severity after an exception decision.
|
||||
- **FR-023**: Downstream review, evidence, and reporting workflows that summarize accepted risk MUST distinguish valid governed exceptions from expired, revoked, rejected, or missing ones.
|
||||
- **FR-024**: The feature MUST introduce at least one positive and one negative authorization test for tenant-context request flows and canonical approval-queue flows.
|
||||
- **FR-025**: The feature MUST introduce regression tests for pending, approved, rejected, renewed, revoked, expired, and missing-exception states, plus wrong-tenant and invalid-transition paths.
|
||||
|
||||
## UI Action Matrix *(mandatory when Filament is changed)*
|
||||
|
||||
| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| Finding Detail Risk Panel | Tenant-context finding inspection under `/admin/t/{tenant}/findings/{finding}` | `Request exception` (`finding_exception.manage`) when no valid exception exists | Linked exception summary card or explicit `View exception` affordance | `View exception`, `Request exception` or `Renew exception` depending on state | None | `Request first exception` when no governance record exists | `Renew exception`, `Revoke exception` when authorized | N/A | Yes | Action labels must describe the governance object, not just the finding status |
|
||||
| Tenant Exception Register | Tenant-context list under `/admin/t/{tenant}/exceptions` | Contextual filters only | Clickable row to exception detail | `View exception`, `Renew exception` or `Revoke exception` depending on state | None in v1 | `Request first exception` | None | N/A | Yes | Inspection-first surface; no bulk approval in first slice |
|
||||
| Canonical Exceptions Queue | Workspace canonical view at `/admin/exceptions` | Contextual filters only | Clickable row to exception detail | `Approve exception`, `Reject exception` for pending items | None in v1 | `Clear filters` | None | N/A | Yes | Queue must remain tenant-safe and only show entitled tenants |
|
||||
| Exception Detail | Tenant or canonical detail inspection surface | None | N/A | None | None | N/A | `Approve exception`, `Reject exception`, `Renew exception`, `Revoke exception` depending on state and capability | N/A | Yes | Detail is an inspection surface, not a disabled edit form |
|
||||
|
||||
### Key Entities *(include if feature involves data)*
|
||||
|
||||
- **Finding Exception**: A governed risk-acceptance record for one finding, including request context, decision state, validity timing, and current governance outcome.
|
||||
- **Exception Decision**: A durable approval, rejection, renewal, or revocation record that explains who made the decision, when, and why.
|
||||
- **Exception Evidence Reference**: A structured pointer to supporting evidence used to justify or review an exception, preserved as intelligible reference metadata.
|
||||
- **Risk Governance Validity**: The normalized truth of whether a finding's accepted-risk posture is currently valid, expiring soon, expired, revoked, rejected, or unsupported.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: An authorized operator can request and route a formal finding exception in under 3 minutes without leaving the product.
|
||||
- **SC-002**: In automated tests, 100% of findings counted as valid accepted risk are backed by a currently valid active exception.
|
||||
- **SC-003**: In acceptance review, an authorized auditor can answer who requested, who approved, why it was accepted, and until when it remains valid within 2 minutes using the product alone.
|
||||
- **SC-004**: Expired, revoked, rejected, and missing-governance accepted-risk states are all distinguishable in automated regression coverage with no false classification as valid active acceptance.
|
||||
- **SC-005**: Negative authorization tests prove that non-members or wrong-tenant users receive deny-as-not-found behavior and in-scope users without the required capability cannot request, approve, renew, or revoke exceptions.
|
||||
- **SC-006**: Renewal and revocation flows preserve prior decision history in automated tests rather than overwriting the previous governance record.
|
||||
|
||||
## Assumptions
|
||||
|
||||
- Spec 111 remains the product source of truth for finding lifecycle and status semantics, including the existing `risk_accepted` status.
|
||||
- Spec 134 remains the source of truth for canonical audit readability and event history behavior.
|
||||
- Evidence linkage may reference evidence snapshots, review artifacts, or other governance evidence when available, but the exception lifecycle must not be blocked merely because evidence is partial.
|
||||
- Normal approval flow should not rely on silent self-approval; any permitted override path must be explicit and auditable.
|
||||
- The first rollout focuses on finding-specific exceptions, not a generic cross-domain waiver engine.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Replacing the existing findings workflow with a different status model
|
||||
- Creating a generic exception platform for every future domain in the first slice
|
||||
- Suppressing or deleting findings automatically when risk is accepted
|
||||
- Making legal or certification claims about compliance acceptance
|
||||
- Replacing evidence snapshots, review packs, or the broader audit foundation with exception-owned storage
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Findings workflow semantics and lifecycle rules from `specs/111-findings-workflow-sla/spec.md`
|
||||
- Audit history foundation and event readability rules from `specs/134-audit-log-foundation/spec.md`
|
||||
- Evidence-domain linkage patterns from `specs/153-evidence-domain-foundation/spec.md` when evidence snapshots are available
|
||||
@ -1,170 +0,0 @@
|
||||
# Feature Specification: Tenant Review Layer
|
||||
|
||||
**Feature Branch**: `001-tenant-review-layer`
|
||||
**Created**: 2026-03-20
|
||||
**Status**: Draft
|
||||
**Input**: User description: "Executive Review Packs / Tenant Review Layer"
|
||||
|
||||
## Spec Scope Fields *(mandatory)*
|
||||
|
||||
- **Scope**: workspace + tenant + canonical-view
|
||||
- **Primary Routes**:
|
||||
- `/admin/t/{tenant}/reviews` as the tenant-scoped review library and entry point for recurring review preparation
|
||||
- `/admin/t/{tenant}/reviews/{review}` as the canonical tenant review inspection surface
|
||||
- `/admin/reviews` as the workspace-scoped canonical review register for entitled operators who manage recurring reviews across multiple tenants
|
||||
- Existing evidence, findings, baseline, and permissions surfaces remain linked drill-down destinations from the review detail when the operator is entitled to inspect them
|
||||
- **Data Ownership**:
|
||||
- Tenant-owned: tenant review records, review composition metadata, review lifecycle state, executive summary content, and stakeholder-facing review-pack references for one tenant
|
||||
- Tenant-owned inputs: evidence snapshots, accepted-risk summaries, findings summaries, baseline or drift posture, permission posture, and operational health summaries that are consumed but not re-owned by the review layer
|
||||
- Workspace-owned but tenant-filtered: canonical review library filters, review schedule summaries, and cross-tenant list presentation state without changing tenant ownership of the review itself
|
||||
- Compliance or framework readiness interpretations remain outside this feature and are not stored as first-class review truth in this slice
|
||||
- **RBAC**:
|
||||
- Workspace membership remains required for every review surface
|
||||
- Tenant entitlement remains required to inspect or mutate tenant-scoped review records
|
||||
- `tenant_review.view` permits listing and inspecting reviews within authorized scope
|
||||
- `tenant_review.manage` permits creating, refreshing, publishing, archiving, and exporting review packs within authorized scope
|
||||
- Non-members or users outside the relevant workspace or tenant scope remain deny-as-not-found, while in-scope members lacking the required capability remain forbidden
|
||||
|
||||
For canonical-view specs, the spec MUST define:
|
||||
|
||||
- **Default filter behavior when tenant-context is active**: When an operator navigates from a tenant into the shared review register, the canonical workspace view opens prefiltered to that tenant. The operator may clear or change the filter only within their authorized tenant set.
|
||||
- **Explicit entitlement checks preventing cross-tenant leakage**: Review queries, counts, tenant labels, filter options, executive summaries, and exported review-pack references must be assembled only after workspace and tenant entitlement checks. Unauthorized users must not learn whether another tenant has review history, stakeholder packs, or upcoming review work.
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - Prepare one tenant review from curated evidence (Priority: P1)
|
||||
|
||||
As a governance operator, I want to create a tenant review from an evidence snapshot and related governance signals, so that quarterly or ad hoc tenant reviews start from one stable, curated review record instead of manual page-by-page assembly.
|
||||
|
||||
**Why this priority**: This is the core product workflow. Without a first-class tenant review record, executive review packs are still ad hoc exports rather than a repeatable review motion.
|
||||
|
||||
**Independent Test**: Can be fully tested by selecting an eligible tenant evidence snapshot, creating a tenant review, and verifying that the resulting review preserves the chosen evidence basis, key governance sections, and summary state even if live source data changes later.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a tenant has at least one eligible evidence snapshot, findings summary, and posture inputs, **When** an authorized operator creates a tenant review, **Then** the system creates one review record that captures the selected evidence basis and generated review sections for that tenant.
|
||||
2. **Given** a tenant review has been created from a specific evidence snapshot, **When** live findings or posture data later change, **Then** the existing review remains tied to its original evidence basis until the operator explicitly refreshes or creates a new review.
|
||||
3. **Given** the chosen evidence basis is partial, **When** the operator creates the review, **Then** the review clearly records which sections are complete, partial, or unavailable rather than implying a fully complete review.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - Present an executive-ready tenant review pack (Priority: P1)
|
||||
|
||||
As an MSP account manager or governance lead, I want a concise executive review surface and exportable review pack for one tenant, so that I can lead customer or management conversations with a stakeholder-ready output rather than raw operational artifacts.
|
||||
|
||||
**Why this priority**: This is the commercial value layer. The product stops being only an operator console when it can produce a readable, stakeholder-facing review output.
|
||||
|
||||
**Independent Test**: Can be fully tested by opening a prepared tenant review, confirming that it presents executive summary sections and drill-down links coherently, and generating a stakeholder-ready review pack from that review without rebuilding the evidence manually.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** an authorized operator opens a prepared tenant review, **When** the review detail loads, **Then** it shows an executive summary, key risks, accepted-risk summary, posture highlights, and recommended next actions in one coherent inspection surface.
|
||||
2. **Given** a tenant review is ready for stakeholder delivery, **When** the operator publishes or exports the executive review pack, **Then** the pack is generated from that review record and reflects the same section ordering and summary truth shown in the product.
|
||||
3. **Given** a stakeholder-facing review pack omits one or more dimensions because the underlying evidence was partial, **When** the operator inspects or exports it, **Then** the omission is explained clearly instead of being silently hidden.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - Manage recurring tenant reviews over time (Priority: P2)
|
||||
|
||||
As a workspace operator, I want a canonical review library across the tenants I manage, so that I can see which tenants were reviewed, which reviews are draft or published, and which tenants need the next review cycle.
|
||||
|
||||
**Why this priority**: Once the first tenant review exists, the product needs a repeatable operating model rather than one-off packs. This enables recurring review discipline and prepares the ground for the later portfolio dashboard.
|
||||
|
||||
**Independent Test**: Can be fully tested by creating reviews for multiple tenants, opening the workspace review register, and verifying that the register shows only entitled tenants with correct lifecycle, publish status, and recency signals.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** an operator is entitled to multiple tenants with review history, **When** they open the workspace review register, **Then** they can filter by tenant, review state, publish status, and review date without seeing unauthorized tenant rows.
|
||||
2. **Given** a tenant already has a published review, **When** the operator starts the next review cycle, **Then** the system creates a new draft review instead of mutating the historical published review.
|
||||
3. **Given** no review matches the current filters, **When** the operator opens the canonical review register, **Then** the empty state explains that no review records match and offers exactly one clear next action.
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- A tenant has eligible evidence snapshots but no valid accepted-risk records; the review must still generate and explicitly show that no governed accepted risks are currently active.
|
||||
- A previously published review pack is revisited after the underlying evidence snapshot expires or is superseded; the historical review must remain intelligible from stored review metadata.
|
||||
- A tenant has multiple evidence snapshots available; the operator must choose which one anchors the review rather than the system silently picking a different basis.
|
||||
- An operator tries to publish or export a review that is still missing required summary sections; the product must fail with a clear readiness reason instead of producing a misleading finished pack.
|
||||
- A workspace operator is entitled to some, but not all, tenants in a workspace; the canonical review register must suppress unauthorized tenant labels, counts, and filter values.
|
||||
- A tenant review is created twice from the same evidence basis without meaningful changes; the system must prevent accidental duplicate published reviews while still allowing a deliberate new draft when needed.
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
**Constitution alignment (required):** This feature introduces a new review-domain data model, user-driven write behavior, and optional long-running generation work for stakeholder-facing review packs, but it does not introduce new Microsoft Graph collection. It must describe the review contract with evidence snapshots, explicit publish/export safety gates, tenant isolation, run observability for any generated pack artifact, and tests. Security-relevant DB-only review lifecycle changes such as publish, archive, and unpublish equivalents must always emit audit history.
|
||||
|
||||
**Constitution alignment (OPS-UX):** If review-pack generation is asynchronous, this feature creates or reuses a dedicated `OperationRun` family for tenant review pack generation and must comply with the Ops-UX 3-surface feedback contract. Start actions may show intent-only feedback. Progress belongs only in the active-ops widget and Monitoring run detail. Review detail may link to the canonical run detail but must not create a parallel progress tracker. `OperationRun.status` and `OperationRun.outcome` remain service-owned through `OperationRunService`. Any `summary_counts` must use allowed numeric-only keys and values. Scheduled or system-initiated review generation must not create initiator-only terminal DB notifications.
|
||||
|
||||
**Constitution alignment (RBAC-UX):** This feature operates in the tenant/admin plane for tenant review detail and mutation surfaces and in the workspace-admin canonical view for the shared review register. Cross-plane access remains deny-as-not-found. Non-members or users outside workspace or tenant scope receive `404`. In-scope users lacking `tenant_review.view` or `tenant_review.manage` receive `403` according to the attempted action. Authorization must be enforced server-side for create, refresh, publish, archive, and export actions. The canonical capability registry remains the only capability source. Destructive-like actions such as archive require confirmation.
|
||||
|
||||
**Constitution alignment (OPS-EX-AUTH-001):** Not applicable. No authentication handshake behavior is changed.
|
||||
|
||||
**Constitution alignment (BADGE-001):** Review lifecycle state, publication state, completeness state, and export readiness are status-like values and must use centralized badge semantics rather than local page-specific mappings. Tests must cover all newly introduced values.
|
||||
|
||||
**Constitution alignment (UI-NAMING-001):** The target object is the tenant review. Operator-facing verbs are `Create review`, `Refresh review`, `Publish review`, `Export executive pack`, and `Archive review`. Source/domain disambiguation is needed only where the review references evidence dimensions such as findings, baseline posture, permissions, or operations health. The same review vocabulary must be preserved across action labels, modal titles, run titles, notifications, and audit prose. Implementation-first terms such as `render package`, `materialize review`, or `hydrate sections` must not become primary operator-facing labels.
|
||||
|
||||
**Constitution alignment (Filament Action Surfaces):** This feature adds or modifies Filament pages/resources for tenant review list and detail plus workspace canonical review register. The Action Surface Contract is satisfied if list inspection uses a canonical inspect affordance, pack generation remains an explicit action, destructive lifecycle actions require confirmation, and all mutations are capability-gated and auditable.
|
||||
|
||||
**Constitution alignment (UX-001 — Layout & Information Architecture):** Review list screens must provide search, sort, and filters for tenant, review state, publication state, evidence basis, and review date. Review detail must use an Infolist-style inspection surface rather than a disabled edit form. Any review-creation form or action must keep inputs inside sections. Empty states must include a specific title, explanation, and exactly one CTA.
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: The system MUST provide a first-class tenant review record that represents one curated governance review for one tenant and one chosen evidence basis.
|
||||
- **FR-002**: A tenant review MUST reference exactly one anchored evidence basis at creation time, with enough stored metadata to remain intelligible if downstream source artifacts later change, expire, or are superseded.
|
||||
- **FR-003**: The first implementation slice MUST support review sections for executive summary, open-risk highlights, accepted-risk summary, permission posture summary, baseline or drift posture summary, and operational health summary.
|
||||
- **FR-004**: The system MUST allow an authorized operator to create a tenant review from an eligible evidence snapshot without manually rebuilding each section from live source pages.
|
||||
- **FR-005**: The system MUST preserve review immutability for published reviews. Refreshing a published review MUST create a new draft review or explicit successor review instead of mutating the published historical record.
|
||||
- **FR-006**: The system MUST distinguish at least draft, ready, published, archived, and superseded review lifecycle states.
|
||||
- **FR-007**: The system MUST record review completeness and section availability explicitly, including when a review is based on partial evidence.
|
||||
- **FR-008**: The system MUST make it clear which evidence dimensions were included, omitted, partial, or stale in each review.
|
||||
- **FR-009**: The system MUST provide one tenant-scoped review library where authorized operators can list, inspect, refresh, publish, archive, and export review records for the active tenant.
|
||||
- **FR-010**: The system MUST provide one workspace-scoped canonical review register where authorized operators can review tenant review history across entitled tenants without leaking unauthorized tenant detail.
|
||||
- **FR-011**: The system MUST provide one stakeholder-facing executive review surface for a prepared tenant review that presents summary content and recommended next steps without forcing the operator into raw source artifacts.
|
||||
- **FR-012**: The system MUST support an exportable executive review pack derived from one prepared tenant review record rather than from ad hoc live assembly.
|
||||
- **FR-013**: Exporting an executive review pack MUST use the selected tenant review as the source of truth for section ordering, summary content, and included dimensions.
|
||||
- **FR-014**: The system MUST block publish or export actions when the review lacks required summary sections or required completeness thresholds for this slice, and it MUST explain the blocking reason clearly.
|
||||
- **FR-015**: The system MUST define duplicate-prevention semantics so that accidental repeated publish or export attempts from the same unchanged review do not create duplicate final artifacts unintentionally.
|
||||
- **FR-016**: The system MUST preserve historical published review records and exported pack references so prior reviews remain auditable and comparable over time.
|
||||
- **FR-017**: Creating, refreshing, publishing, archiving, and exporting a review MUST be recorded in audit history with workspace scope, tenant scope, actor, action, and outcome.
|
||||
- **FR-018**: The feature MUST explicitly exclude framework-oriented compliance scoring, certification claims, and BSI, NIS2, or CIS mapping from the first slice. Those remain a downstream Compliance Readiness feature.
|
||||
- **FR-019**: The feature MUST introduce at least one positive and one negative authorization test for tenant-scoped review management and workspace-scoped canonical review visibility.
|
||||
- **FR-020**: The feature MUST introduce regression tests proving evidence-basis anchoring, published-review immutability, executive-pack consistency, and cross-tenant isolation.
|
||||
|
||||
## UI Action Matrix *(mandatory when Filament is changed)*
|
||||
|
||||
| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| Tenant Review Library | Tenant-context review list under `/admin/t/{tenant}/reviews` | `Create review` (`tenant_review.manage`) | Clickable row to review detail | `View review`, `Export executive pack` when ready | None in v1 | `Create first review` | None | N/A | Yes | Create may use an action modal because it selects an evidence basis and starts a review composition workflow |
|
||||
| Tenant Review Detail | Canonical detail route under `/admin/t/{tenant}/reviews/{review}` | None | N/A | None | None | N/A | `Refresh review`, `Publish review`, `Export executive pack`, `Archive review` | N/A | Yes | Inspection surface only; no disabled edit form |
|
||||
| Workspace Review Register | Workspace canonical view at `/admin/reviews` | `Clear filters` | Clickable row to review detail | `View review`, `Export executive pack` when authorized | None in v1 | `Clear filters` | None | N/A | Export yes | Must suppress unauthorized tenant rows and filter values |
|
||||
|
||||
### Key Entities *(include if feature involves data)*
|
||||
|
||||
- **Tenant Review**: A curated review record for one tenant anchored to a chosen evidence basis and used for recurring governance conversations.
|
||||
- **Review Section**: One named portion of the tenant review, such as executive summary, risk highlights, posture summary, or operational health summary, including its completeness and source references.
|
||||
- **Executive Review Pack**: A stakeholder-facing deliverable derived from one tenant review and preserving that review's section ordering, summary truth, and completeness disclosures.
|
||||
- **Review Lifecycle State**: The normalized state of a tenant review, including draft, ready, published, archived, and superseded.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: An authorized operator can create a tenant review from an eligible evidence basis and open its executive summary in under 3 minutes without leaving the product.
|
||||
- **SC-002**: Published tenant reviews remain unchanged in 100% of automated immutability tests after underlying live source records are modified.
|
||||
- **SC-003**: In manual review flow validation, an operator can answer the tenant's top risks, current posture highlights, and next actions from one review detail surface without opening more than one optional drill-down page.
|
||||
- **SC-004**: Exported executive review packs match their source tenant review's included dimensions and summary ordering in 100% of automated integration tests for the covered first-slice review sections.
|
||||
- **SC-005**: Negative authorization tests prove that non-members or wrong-tenant users receive deny-as-not-found behavior and in-scope users without the required capability cannot create, publish, archive, or export tenant reviews.
|
||||
- **SC-006**: Operators can distinguish draft, ready, published, archived, and superseded review states in one inspection step from list or detail surfaces.
|
||||
|
||||
## Assumptions
|
||||
|
||||
- Evidence snapshots are the primary source-of-truth input for review creation in the first slice.
|
||||
- Findings summaries, accepted-risk lifecycle data, permission posture, and baseline or drift posture are mature enough to populate first-slice review sections.
|
||||
- The first slice optimizes for tenant-by-tenant recurring reviews and executive packs, not for framework-oriented compliance mapping.
|
||||
- Workspace-level review visibility is a register and management surface, not yet a portfolio dashboard with SLA analytics.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Building a framework-oriented Compliance Readiness layer with BSI, NIS2, or CIS mapping
|
||||
- Creating tenant portfolio rollups, SLA health dashboards, or fleet ranking views across tenants
|
||||
- Implementing cross-tenant compare or promotion workflows
|
||||
- Turning the tenant review layer into a generic BI reporting system
|
||||
- Triggering new Microsoft Graph collection during review preparation
|
||||
36
specs/200-filament-surface-rules/checklists/requirements.md
Normal file
36
specs/200-filament-surface-rules/checklists/requirements.md
Normal file
@ -0,0 +1,36 @@
|
||||
# Specification Quality Checklist: UI/UX Constitution Extension: Filament Nativity & Custom Surface Rules
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2026-04-18
|
||||
**Feature**: [spec.md](../spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [x] No implementation details (languages, frameworks, APIs)
|
||||
- [x] Focused on user value and business needs
|
||||
- [x] Written for non-technical stakeholders
|
||||
- [x] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||
- [x] Requirements are testable and unambiguous
|
||||
- [x] Success criteria are measurable
|
||||
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||
- [x] All acceptance scenarios are defined
|
||||
- [x] Edge cases are identified
|
||||
- [x] Scope is clearly bounded
|
||||
- [x] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria
|
||||
- [x] User scenarios cover primary flows
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [x] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- Optional operator-surface classification tables are intentionally omitted because this feature amends repository governance rather than changing a concrete runtime surface.
|
||||
- Filament and Livewire terminology is intentional product vocabulary for this constitution slice, not code-level implementation guidance.
|
||||
- Validation cross-checked the spec against adjacent Specs 196 through 199 and the existing constitution anchors UI-FIL-001, UI-EX-001, DECIDE-001, UI-REVIEW-001, and HDR-001.
|
||||
@ -0,0 +1,103 @@
|
||||
# Constitution Governance Contract
|
||||
|
||||
## Contract Type
|
||||
|
||||
Docs-only governance contract. This feature introduces no runtime HTTP, GraphQL, CLI, or queue API surface.
|
||||
|
||||
## Governing Scope
|
||||
|
||||
Spec 200 must amend the existing UI constitution so the following rule families become explicit, reviewable, and bounded:
|
||||
|
||||
- Filament-native by default
|
||||
- fake-native prohibitions
|
||||
- legitimate custom-surface allowance
|
||||
- shared detail micro-UI family rules
|
||||
- shell/page/detail state ownership rules
|
||||
- reviewer-facing guidance and classification questions
|
||||
- explicit exception handling
|
||||
- named anti-pattern catalog
|
||||
|
||||
## Required Amendment Targets
|
||||
|
||||
The implementation must update the existing constitution in `.specify/memory/constitution.md` rather than create a separate rulebook.
|
||||
|
||||
Expected amendment targets:
|
||||
|
||||
- `UI-SURF-001`
|
||||
- `ACTSURF-001`
|
||||
- `HDR-001`
|
||||
- `UI-HARD-001`
|
||||
- `UI-EX-001`
|
||||
- `UI-REVIEW-001`
|
||||
- `Filament UI — Action Surface Contract`
|
||||
- `UX-001`
|
||||
- `UI-FIL-001` where native-first wording needs sharpening
|
||||
|
||||
## Final Amendment Mapping
|
||||
|
||||
| Source spec | Problem class absorbed by Spec 200 | Final constitution targets |
|
||||
|---|---|---|
|
||||
| Spec 196 | Native-by-default clarity, fake-native drift, request-driven body-state misuse, simple-overview drift | `UI-FIL-001`, `UI-HARD-001`, `UI-EX-001`, `UI-REVIEW-001` |
|
||||
| Spec 197 | Shared detail micro-UI families, host/core boundaries, bounded host variation | `UI-SURF-001`, `ACTSURF-001`, `HDR-001`, `Filament UI — Action Surface Contract`, `UI-REVIEW-001` |
|
||||
| Spec 198 | Requested vs active vs draft vs inspect vs restorable page-state ownership | `UI-HARD-001`, `UX-001`, `UI-REVIEW-001` |
|
||||
| Spec 199 | Workspace-first shell truth, tenantless state, shell/page separation, fallback clarity | `ACTSURF-001`, `UX-001`, `UI-REVIEW-001` |
|
||||
| Spec 201 | Enforcement follow-up only | deferred consumer; no constitution wording invented there |
|
||||
|
||||
## Final Exception Boundary
|
||||
|
||||
Spec 200 leaves four bounded exception families available inside `UI-EX-001`:
|
||||
|
||||
- `Legitimate Custom Surface Exception`
|
||||
- `Nativity Exception`
|
||||
- `Shared Detail Host Variation Exception`
|
||||
- `State-Layer Special-case Exception`
|
||||
|
||||
Each exception must stay inside the existing exception model and must state:
|
||||
|
||||
1. the product reason
|
||||
2. the smallest custom behavior required
|
||||
3. what remains standardized
|
||||
4. which layer owns the relevant state
|
||||
5. what Spec 201 may later enforce
|
||||
|
||||
## Acceptance Contract
|
||||
|
||||
The finished constitution amendment must satisfy all of the following:
|
||||
|
||||
1. A reviewer can classify the representative cases from Specs 196, 197, 198, and 199 using the amended constitution alone.
|
||||
2. The amendment does not create a parallel top-level UI rulebook.
|
||||
3. Legitimate custom surfaces remain possible through an explicit exception path.
|
||||
4. The anti-pattern catalog names at least the recurring drift classes identified in the feature spec.
|
||||
5. Reviewer-facing guidance and classification questions are explicit enough to classify the representative cases from Specs 196 through 199.
|
||||
6. The cross-spec mapping to Specs 196 through 199 and the deferral to Spec 201 are explicit.
|
||||
|
||||
The acceptance contract is not satisfied unless the constitution now names all of the following review classes directly: `Native Surface`, `Fake-Native Surface`, `Custom Surface`, `Shared Detail Micro-UI`, `Host`, `Global Context State`, `Page State`, `Detail State`, `Legitimate Exception`, `Host Drift`, `State Layer Collapse`, and `Parallel Inspect Worlds`.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
This contract explicitly excludes:
|
||||
|
||||
- `app/` runtime code changes
|
||||
- route, controller, or Livewire behavior changes
|
||||
- CI/grep/lint/test enforcement
|
||||
- checklist-template operationalization beyond optional wording-only references
|
||||
- fabricated REST or GraphQL endpoints
|
||||
|
||||
## Deferred Enforcement Boundary
|
||||
|
||||
Spec 201 is responsible for operationalization work such as:
|
||||
|
||||
- review checklist changes
|
||||
- grep/lint guards
|
||||
- CI enforcement
|
||||
- runtime or test-backed regression guards for the anti-pattern catalog
|
||||
|
||||
Spec 200 must define the rule language cleanly enough that Spec 201 can consume it without inventing a new vocabulary.
|
||||
|
||||
## Close-out Contract
|
||||
|
||||
The final artifact set must leave one explicit close-out note that separates:
|
||||
|
||||
- newly added clauses and vocabulary
|
||||
- tightened existing clauses
|
||||
- enforcement work intentionally deferred to Spec 201
|
||||
253
specs/200-filament-surface-rules/data-model.md
Normal file
253
specs/200-filament-surface-rules/data-model.md
Normal file
@ -0,0 +1,253 @@
|
||||
# Data Model: UI/UX Constitution Extension: Filament Nativity & Custom Surface Rules
|
||||
|
||||
## Overview
|
||||
|
||||
Spec 200 is a docs-only governance feature. Its data model is conceptual rather than persisted: it describes the rule objects, vocabulary, and mappings that the constitution amendment must carry so reviewers can classify future UI work consistently.
|
||||
|
||||
No application database schema, runtime DTO, or transport contract is introduced by this feature.
|
||||
|
||||
## Entities
|
||||
|
||||
### 1. ConstitutionAmendmentTarget
|
||||
|
||||
Represents one existing constitution section that Spec 200 extends.
|
||||
|
||||
**Fields**
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `section_id` | string | Existing rule ID or section anchor, for example `UI-SURF-001` or `UI-EX-001` |
|
||||
| `target_file` | string | File path, always `.specify/memory/constitution.md` for this feature |
|
||||
| `amendment_type` | enum | `clarification`, `extension`, or `new-clause` |
|
||||
| `problem_classes` | list<string> | The drift classes this amendment addresses |
|
||||
| `source_specs` | list<string> | Evidence specs feeding the amendment, limited here to Specs 196 through 199 |
|
||||
| `expected_outputs` | list<string> | Vocabulary, anti-pattern, exception, or state-layer additions contributed by this target |
|
||||
| `deferred_enforcement` | list<string> | What remains reserved for Spec 201 |
|
||||
|
||||
**Validation rules**
|
||||
|
||||
- `section_id` must reference an already existing constitution section.
|
||||
- `amendment_type` must not imply a new parallel rulebook.
|
||||
- Every target must cite at least one source spec or one direct repo problem class.
|
||||
|
||||
### 2. SurfaceVocabularyTerm
|
||||
|
||||
Represents a named concept that future specs and reviews must use consistently.
|
||||
|
||||
**Fields**
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `name` | string | Canonical term, such as `Native Surface` or `Fake-Native Surface` |
|
||||
| `definition` | text | Reviewable product definition |
|
||||
| `positive_examples` | list<string> | Example surfaces or surface families that fit the term |
|
||||
| `negative_examples` | list<string> | Nearby cases that must not be misclassified under the term |
|
||||
| `governing_sections` | list<string> | Constitution sections that must mention or support the term |
|
||||
| `source_specs` | list<string> | Adjacent specs that proved the need for the term |
|
||||
|
||||
**Validation rules**
|
||||
|
||||
- Every term must map to at least one amended constitution section.
|
||||
- Terms must describe product-review concepts, not implementation jargon.
|
||||
- Terms must be classifiable from real repo cases.
|
||||
|
||||
### 3. AntiPattern
|
||||
|
||||
Represents a named failure mode the constitution must forbid or flag.
|
||||
|
||||
**Fields**
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `name` | string | Canonical anti-pattern name, such as `Filament Costume` |
|
||||
| `definition` | text | What the anti-pattern looks like in practice |
|
||||
| `trigger_signals` | list<string> | Observable characteristics that let reviewers identify it |
|
||||
| `default_review_outcome` | enum | `reject`, `document-exception`, or `defer-to-follow-up-spec` |
|
||||
| `allowed_exception_path` | string | Exception type or `none` when no escape is expected |
|
||||
| `source_specs` | list<string> | Spec evidence behind the anti-pattern |
|
||||
|
||||
**Validation rules**
|
||||
|
||||
- Every anti-pattern must have at least one trigger signal.
|
||||
- Every anti-pattern must either forbid the pattern directly or point to a bounded exception path.
|
||||
- Anti-pattern names must remain stable enough for future guardrails in Spec 201.
|
||||
|
||||
### 4. ExceptionType
|
||||
|
||||
Represents a bounded, legitimate deviation from the default rules.
|
||||
|
||||
**Fields**
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `name` | string | Canonical exception name |
|
||||
| `allowed_when` | text | Product reason that makes the exception legitimate |
|
||||
| `required_justification` | list<string> | Mandatory explanation points in the governing spec or PR |
|
||||
| `boundaries` | list<string> | What the exception does not allow |
|
||||
| `standardized_parts` | list<string> | What must remain consistent despite the exception |
|
||||
| `governing_section` | string | Expected home in `UI-EX-001` |
|
||||
| `deferred_enforcement` | list<string> | Which parts Spec 201 may later operationalize |
|
||||
|
||||
**Validation rules**
|
||||
|
||||
- Every exception type must stay inside the existing exception model.
|
||||
- `allowed_when` must describe product need, not convenience.
|
||||
- Every exception must state what stays standardized.
|
||||
|
||||
### 5. StateOwnershipRule
|
||||
|
||||
Represents the review contract for shell, page, or detail state.
|
||||
|
||||
**Fields**
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `layer` | enum | `shell`, `page`, or `detail` |
|
||||
| `owns_truth` | text | What this layer is authoritative for |
|
||||
| `allowed_inputs` | list<string> | Inputs the layer may consume, such as route state, query seed, or local viewer state |
|
||||
| `query_role` | enum | `initialization-only`, `durable`, `deeplink-only`, or `unsupported` |
|
||||
| `forbidden_overlaps` | list<string> | Competing truths or cross-layer conflicts that are not allowed |
|
||||
| `source_specs` | list<string> | Spec 198 and/or 199 evidence |
|
||||
|
||||
**Validation rules**
|
||||
|
||||
- Each rule must assign exactly one owner layer.
|
||||
- A rule may describe inputs from other layers, but it may not assign equal authority to multiple layers.
|
||||
- Query role must be explicit whenever the layer can be initialized from URL or remembered state.
|
||||
|
||||
### 6. CrossSpecMapping
|
||||
|
||||
Represents how one adjacent spec feeds the constitution amendment and where follow-up work goes.
|
||||
|
||||
**Fields**
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `source_spec` | string | `196`, `197`, `198`, `199`, or `201` |
|
||||
| `problem_class` | string | Problem family contributed by that spec |
|
||||
| `constitution_targets` | list<string> | Which amendment targets consume that evidence |
|
||||
| `status` | enum | `evidence-source`, `consumed-by-spec-200`, or `deferred-to-201` |
|
||||
| `notes` | text | Short mapping summary |
|
||||
|
||||
**Validation rules**
|
||||
|
||||
- Specs 196 through 199 must map into at least one amendment target.
|
||||
- Spec 201 must appear only as a deferred enforcement consumer, not as an implementation dependency within Spec 200.
|
||||
|
||||
## Relationships
|
||||
|
||||
- A `ConstitutionAmendmentTarget` produces zero or more `SurfaceVocabularyTerm` entries.
|
||||
- A `ConstitutionAmendmentTarget` may define zero or more `AntiPattern` and `ExceptionType` entries.
|
||||
- `StateOwnershipRule` entries are a specialized conceptual rule family that feed both vocabulary terms and amendment targets.
|
||||
- `CrossSpecMapping` connects the adjacent evidence specs to the exact constitution targets that absorb them.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
### Amendment Lifecycle
|
||||
|
||||
1. **Identified**: A rule gap is proven by a current repo case.
|
||||
2. **Mapped**: The gap is assigned to an existing constitution section.
|
||||
3. **Integrated**: The constitution text is amended with vocabulary, anti-patterns, or exceptions.
|
||||
4. **Reviewable**: Representative cases from Specs 196 through 199 can be classified through the amended language.
|
||||
5. **Operationalized later**: Spec 201 may add checklist, grep, lint, or test enforcement based on the integrated rule.
|
||||
|
||||
### Exception Lifecycle
|
||||
|
||||
1. **Requested**: A surface claims that native-by-default or shared-contract rules do not fit.
|
||||
2. **Justified**: The spec states the product reason, boundaries, and standardized parts.
|
||||
3. **Accepted or rejected**: Review uses the exception type and anti-pattern catalog.
|
||||
4. **Potentially enforceable later**: Spec 201 may formalize recurring exception checks.
|
||||
|
||||
## Derived Outputs
|
||||
|
||||
The conceptual data model must support these concrete outputs in the constitution amendment:
|
||||
|
||||
- glossary terms for the required vocabulary
|
||||
- named anti-pattern entries
|
||||
- explicit exception types or exception clauses
|
||||
- state ownership guidance for shell/page/detail separation
|
||||
- a mapping note connecting Specs 196 through 199 to the amended rule family
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- database tables
|
||||
- Eloquent models
|
||||
- runtime registries or service abstractions
|
||||
- REST or GraphQL transport schemas
|
||||
- executable state machines
|
||||
- template or CI enforcement logic in this spec
|
||||
|
||||
## Amendment Target Inventory
|
||||
|
||||
| Amendment target | Amendment type | Problem classes absorbed | Source specs | Expected outputs | Deferred enforcement |
|
||||
|---|---|---|---|---|---|
|
||||
| `UI-FIL-001` | extension | native-by-default, fake-native drift, simple-overview nativity | 196 | `Native Surface`, `Custom Surface`, `Fake-Native Surface`, anti-pattern anchors | Spec 201 guardrails for fake-native patterns |
|
||||
| `UI-HARD-001` | extension | forbidden fake-native behavior, one-primary-interaction-model, state/inspect conflict classes | 196, 198 | anti-pattern catalog, hard prohibitions, state-layer violation names | Spec 201 grep/test checks for forbidden patterns |
|
||||
| `UI-EX-001` | extension | bounded legitimate custom surfaces, nativity exceptions, host variation, state special cases | 196, 197, 198 | finalized exception families and guardrails | Spec 201 checklist and exception review enforcement |
|
||||
| `UI-SURF-001` | extension | shared detail family taxonomy, host terminology | 197 | `Shared Detail Micro-UI`, `Host` | Spec 201 metadata/review guardrails |
|
||||
| `ACTSURF-001` | extension | one primary interaction model, host/family action ownership, shell/page separation pressure | 197, 199 | host/core action rules, `Parallel Inspect Worlds` review questions | Spec 201 action-surface enforcement |
|
||||
| `HDR-001` | clarification | host-owned header discipline for embedded shared families | 197 | header-specific host boundary language | Spec 201 header review prompts |
|
||||
| `Filament UI — Action Surface Contract` | extension | native simple-overview rule, shared-family contract ownership, explicit state ownership disclosure | 196, 197, 198 | Filament-facing execution rules | Spec 201 checklist/guard uptake |
|
||||
| `UX-001` | extension | shell/page/detail ownership, requested/active/draft/inspect/restorable roles | 198, 199 | state vocabulary and owner-layer rules | Spec 201 review and doc enforcement |
|
||||
| `UI-REVIEW-001` | extension | reviewer-facing classification, anti-pattern checks, state-owner questions | 196, 197, 198, 199 | explicit review questions and red flags | Spec 201 operationalization |
|
||||
|
||||
## Final Vocabulary Inventory
|
||||
|
||||
| Term | Governing sections | Source specs | Role in the amendment |
|
||||
|---|---|---|---|
|
||||
| `Native Surface` | `UI-SURF-001`, `UI-FIL-001`, `UI-HARD-001` | 196 | Default classification for standard Filament/shared-primitives work |
|
||||
| `Fake-Native Surface` | `UI-HARD-001`, `UI-FIL-001`, `UI-REVIEW-001` | 196 | Forbidden violation class for Filament-looking but contract-foreign UI |
|
||||
| `Custom Surface` | `UI-SURF-001`, `UI-EX-001`, `UI-FIL-001` | 196 | Legitimate non-native surface only with bounded product reason |
|
||||
| `Legitimate Exception` | `UI-EX-001`, `UI-REVIEW-001` | 196 | Shared language for approved deviations |
|
||||
| `Shared Detail Micro-UI` | `UI-SURF-001`, `ACTSURF-001`, `Filament UI — Action Surface Contract` | 197 | Repeated embedded family that must keep one shared contract |
|
||||
| `Host` | `UI-SURF-001`, `ACTSURF-001`, `HDR-001` | 197 | Parent page/resource/workbench owning route, auth, and host-only actions |
|
||||
| `Global Context State` | `UX-001`, `UI-REVIEW-001` | 199 | Shell-owned workspace/tenant truth |
|
||||
| `Page State` | `UX-001`, `UI-REVIEW-001` | 198 | Page-owned filter/tab/mode/selection truth |
|
||||
| `Detail State` | `UX-001`, `UI-REVIEW-001` | 197, 198 | Embedded viewer or inner inspect truth subordinate to page/shell |
|
||||
| `Requested State` | `UX-001`, `UI-REVIEW-001` | 198, 199 | Input state before validation or hydration |
|
||||
| `Active State` | `UX-001`, `UI-REVIEW-001` | 198, 199 | Current governing validated state |
|
||||
| `Draft State` | `UX-001`, `UI-REVIEW-001` | 198 | Pending local state not yet applied |
|
||||
| `Inspect State` | `UI-HARD-001`, `UX-001`, `UI-REVIEW-001` | 198 | Selected-record/detail focus truth |
|
||||
| `Restorable State` | `UX-001`, `UI-REVIEW-001` | 198 | The shareable subset intentionally recreated |
|
||||
| `Host Drift` | `UI-HARD-001`, `UI-REVIEW-001` | 197 | Forbidden host-side rewrite of shared-family core semantics |
|
||||
| `State Layer Collapse` | `UI-HARD-001`, `UX-001`, `UI-REVIEW-001` | 198, 199 | Forbidden multi-layer ownership of the same truth |
|
||||
| `Parallel Inspect Worlds` | `ACTSURF-001`, `UI-HARD-001`, `UI-REVIEW-001` | 198 | Forbidden competing inspect/view models for one concern |
|
||||
|
||||
## Final Anti-pattern Catalog
|
||||
|
||||
| Anti-pattern | Trigger signals | Default review outcome | Allowed exception path | Source specs |
|
||||
|---|---|---|---|---|
|
||||
| `Filament Costume` | Raw HTML/Tailwind controls mimic Filament semantics that native/shared primitives already provide | reject | `Nativity Exception` only when the semantic gap is explicit and narrow | 196 |
|
||||
| `Blade Request UI` | Primary body-state contract depends on `request()`, GET forms, or manual query parsing inside an active Filament surface | reject | `Nativity Exception` only for initialization-only request input | 196 |
|
||||
| `Hand-Rolled Simple Overview` | Simple report/overview with ordinary columns, filters, empty states, and navigation is rebuilt as bespoke markup | reject | `Legitimate Custom Surface Exception` only when the product need is materially richer than a table/list | 196 |
|
||||
| `Hidden Exception` | Custom behavior survives through history or convenience without a named exception block | reject | none | 196 |
|
||||
| `Host Drift` | One host changes core family zones, view semantics, or diagnostics contract without declaring host-scoped variation | reject | `Shared Detail Host Variation Exception` | 197 |
|
||||
| `State Layer Collapse` | Shell, page, or detail state each claim the same active truth or restoration role | reject | `State-Layer Special-case Exception` only when the owner hierarchy stays explicit | 198, 199 |
|
||||
| `Parallel Inspect Worlds` | Two same-concern inspect/open/select/view contracts coexist as peers | reject | none | 198 |
|
||||
|
||||
## Final Exception Relationships
|
||||
|
||||
| Exception type | Solves | Governing section | Standardized parts that must remain intact | Related anti-patterns |
|
||||
|---|---|---|---|---|
|
||||
| `Legitimate Custom Surface Exception` | Rich visualization, diagnostic/review work, multi-zone evidence, or shell-context-specific UI that does not fit standard CRUD/overview semantics | `UI-EX-001` | canonical nouns, scope clarity, explicit action hierarchy, explicit state ownership | `Hand-Rolled Simple Overview`, `Hidden Exception` |
|
||||
| `Nativity Exception` | Filament/shared primitives cannot express the required semantics cleanly | `UI-EX-001` | native/shared surrounding controls, no local status language, no request-owned primary body state | `Filament Costume`, `Blade Request UI`, `Hidden Exception` |
|
||||
| `Shared Detail Host Variation Exception` | A known shared family needs bounded host framing, assist entry, or optional-zone variation | `UI-EX-001` | family core zones, next-step contract, diagnostics contract, primary view/inspect model | `Host Drift` |
|
||||
| `State-Layer Special-case Exception` | A page legitimately needs explicit requested/active/draft/inspect/restorable distinctions beyond the simple default | `UI-EX-001` | one owner per layer, explicit restorable subset, explicit query role, no silent shell/page overlap | `State Layer Collapse`, `Parallel Inspect Worlds` |
|
||||
|
||||
## Final State Ownership Rules
|
||||
|
||||
| Layer | Owns truth | Allowed inputs | Query role | Forbidden overlaps | Source specs |
|
||||
|---|---|---|---|---|---|
|
||||
| `shell` | workspace/tenant context, tenantless state, shell recovery state | route context, explicit switch/select/clear flows, valid restore candidates | durable only when the shell contract explicitly allows restore | page tabs/filters owning workspace truth, detail viewers owning tenant scope | 199 |
|
||||
| `page` | filters, tabs, active modes, selected-record/page-level inspect state, applied analysis state | shell context, deeplink/init state, local interactions, explicit draft/apply actions | initialization-only, durable, or unsupported exactly as declared by the page contract | shell precedence logic, detail-local state redefining page truth | 198 |
|
||||
| `detail` | embedded viewer state, inner section/tab choice, family-local assist or reveal state | host/page state, local viewer controls | usually unsupported or local-only unless an exception documents otherwise | shell or page ownership of the same active inspect/view truth | 197, 198 |
|
||||
|
||||
## Final Cross-Spec Mapping
|
||||
|
||||
| Source spec | Problem class | Constitution targets | Status | Notes |
|
||||
|---|---|---|---|---|
|
||||
| `196` | Fake-native drift and simple-overview nativity | `UI-FIL-001`, `UI-HARD-001`, `UI-EX-001`, `UI-REVIEW-001` | consumed-by-spec-200 | Produces native/custom/fake-native language and the anti-pattern anchors |
|
||||
| `197` | Shared detail micro-UI host/core boundaries | `UI-SURF-001`, `ACTSURF-001`, `HDR-001`, `Filament UI — Action Surface Contract`, `UI-REVIEW-001` | consumed-by-spec-200 | Produces shared-family, host, host-drift, and header-boundary language |
|
||||
| `198` | Page-state ownership and inspect/restoration semantics | `UI-HARD-001`, `UX-001`, `UI-REVIEW-001` | consumed-by-spec-200 | Produces requested/active/draft/inspect/restorable vocabulary and inspect-conflict rules |
|
||||
| `199` | Shell-context truth and shell/page separation | `ACTSURF-001`, `UX-001`, `UI-REVIEW-001` | consumed-by-spec-200 | Produces global-context/page/detail ownership clarity |
|
||||
| `201` | Review, grep, lint, and regression enforcement | all of the above as downstream consumers | deferred-to-201 | Spec 201 consumes the final vocabulary directly and must not invent replacement categories |
|
||||
171
specs/200-filament-surface-rules/plan.md
Normal file
171
specs/200-filament-surface-rules/plan.md
Normal file
@ -0,0 +1,171 @@
|
||||
# Implementation Plan: UI/UX Constitution Extension: Filament Nativity & Custom Surface Rules
|
||||
|
||||
**Branch**: `200-filament-surface-rules` | **Date**: 2026-04-18 | **Spec**: `specs/200-filament-surface-rules/spec.md`
|
||||
**Input**: Feature specification from `specs/200-filament-surface-rules/spec.md`
|
||||
|
||||
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.
|
||||
|
||||
## Summary
|
||||
|
||||
Extend the existing UI constitution so Filament-native defaults, fake-native drift, legitimate custom surfaces, shared detail micro-UI families, and shell/page/detail state ownership are all reviewable through one integrated rule set. The implementation approach is docs-only: amend existing constitution sections rather than create a parallel UI rulebook, add the shared vocabulary and anti-pattern catalog grounded in Specs 196 through 199, define the exception model boundaries, and record a clean handoff to Spec 201 for enforcement.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: Markdown governance artifacts in a PHP 8.4.15 / Laravel 12 / Filament v5 / Livewire v4 repository
|
||||
**Primary Dependencies**: `.specify/memory/constitution.md`, `docs/ui/operator-ux-surface-standards.md`, adjacent Specs 196 through 199, existing UI rule IDs `UI-SURF-001`, `ACTSURF-001`, `HDR-001`, `UI-HARD-001`, `UI-EX-001`, `UI-REVIEW-001`, `UI-FIL-001`, `DECIDE-001`, and `UX-001`
|
||||
**Storage**: N/A
|
||||
**Testing**: Document review, representative-case validation, and checklist verification only
|
||||
**Validation Lanes**: N/A (docs-only governance change)
|
||||
**Target Platform**: Repository governance for the Laravel web application and its Filament admin/operator surfaces
|
||||
**Project Type**: Laravel monolith with docs-only planning artifacts
|
||||
**Performance Goals**: N/A; the success target is review clarity, constitution precision, and explicit scope boundaries rather than runtime performance
|
||||
**Constraints**: No parallel rulebook; no runtime routes or API contracts; no CI/grep/lint/test enforcement in this spec; legitimate custom surfaces must remain possible; Spec 201 remains the enforcement boundary
|
||||
**Scale/Scope**: One constitution amendment path, one supporting operator-UX standards reference, one spec artifact set, and cross-spec mapping across Specs 196 through 199 plus Spec 201
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
- Inventory-first: N/A (docs-only governance feature; no inventory or snapshot behavior changes)
|
||||
- Read/write separation: PASS (no product write path is introduced)
|
||||
- Graph contract path: PASS (no Graph calls or contract registry changes)
|
||||
- Deterministic capabilities: PASS (no capability derivation changes)
|
||||
- RBAC-UX: PASS (no authorization behavior changes; planned rules only reinforce existing boundaries)
|
||||
- Workspace isolation: PASS (no new workspace context path; shell/page/detail layering rules must preserve established workspace truth)
|
||||
- Tenant isolation: PASS (no new tenant access path; future rules explicitly forbid silent scope broadening)
|
||||
- Run observability: PASS (no `OperationRun` creation or lifecycle changes)
|
||||
- Ops-UX 3-surface feedback: N/A (no run feedback surfaces)
|
||||
- Ops-UX lifecycle + summary counts + guards: N/A (no run lifecycle change)
|
||||
- Ops-UX system runs: N/A
|
||||
- Automation: N/A (no queued/scheduled flow)
|
||||
- Data minimization: PASS (no runtime or persistence impact)
|
||||
- Test governance (TEST-GOV-001): PASS (`N/A` lanes and no runtime impact are explicit; enforcement-oriented follow-up is deferred to Spec 201)
|
||||
- Proportionality (PROP-001): PASS but triggered; the new vocabulary and taxonomy are bounded to already proven repo problem classes and do not create runtime architecture
|
||||
- No premature abstraction (ABSTR-001): PASS (no new runtime factory, registry, resolver, or service layer is introduced)
|
||||
- Persisted truth (PERSIST-001): PASS (repository docs only; no product truth added)
|
||||
- Behavioral state (STATE-001): PASS (state-layer terms are governance vocabulary, not new persisted or executable state families)
|
||||
- UI semantics (UI-SEM-001): PASS (the plan strengthens direct, reviewable domain-to-UI rules and rejects local semantic frameworks)
|
||||
- V1 explicitness / few layers (V1-EXP-001, LAYER-001): PASS (existing constitution sections are amended in place rather than layered with a second framework)
|
||||
- Spec discipline / bloat check (SPEC-DISC-001, BLOAT-001): PASS (all related semantic additions stay in one rule spec; enforcement stays separate in Spec 201)
|
||||
- Badge semantics (BADGE-001): PASS (no new badge taxonomy; the plan only clarifies when local status language is forbidden)
|
||||
- Filament-native UI (UI-FIL-001): PASS (this feature extends the rule instead of introducing alternative UI semantics)
|
||||
- UI/UX surface taxonomy (UI-CONST-001 / UI-SURF-001): PASS (the plan tightens existing taxonomy and exception logic)
|
||||
- Decision-first operating model (DECIDE-001): PASS (no new primary surface; the plan strengthens review vocabulary for future surfaces)
|
||||
- UI/UX inspect model and hard rules (UI-HARD-001): PASS (the plan adds fake-native and state-layer clarity without changing runtime inspect models)
|
||||
- Action-surface discipline (ACTSURF-001 / HDR-001 / UI-EX-001): PASS (the plan extends existing action and exception rules in place)
|
||||
|
||||
Gate status before Phase 0 research: PASS
|
||||
|
||||
## Test Governance Check
|
||||
|
||||
> **Fill for any runtime-changing or test-affecting feature. Docs-only or template-only work may state concise `N/A` or `none`.**
|
||||
|
||||
- **Test purpose / classification by changed surface**: N/A
|
||||
- **Affected validation lanes**: N/A
|
||||
- **Why this lane mix is the narrowest sufficient proof**: This feature is a docs-only governance and planning slice. Validation is review-oriented and does not require runtime or test-lane execution.
|
||||
- **Narrowest proving command(s)**: N/A
|
||||
- **Fixture / helper / factory / seed / context cost risks**: none
|
||||
- **Expensive defaults or shared helper growth introduced?**: no
|
||||
- **Heavy-family additions, promotions, or visibility changes**: none
|
||||
- **Closing validation and reviewer handoff**: Reviewers should verify the amended constitution against the representative cases from Specs 196 through 199, confirm that no runtime enforcement is claimed prematurely, and ensure the handoff to Spec 201 is explicit.
|
||||
- **Budget / baseline / trend follow-up**: none
|
||||
- **Review-stop questions**: Does any rule create a parallel rulebook? Does any clause imply runtime enforcement that belongs to Spec 201? Does the taxonomy overreach beyond proven repo cases? Does the docs-only contract remain clearly bounded?
|
||||
- **Escalation path**: none
|
||||
- **Why no dedicated follow-up spec is needed**: A dedicated follow-up already exists as Spec 201; this plan only prepares the vocabulary and constitution amendments that Spec 201 will later operationalize.
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/200-filament-surface-rules/
|
||||
├── plan.md
|
||||
├── checklists/
|
||||
│ └── requirements.md
|
||||
├── research.md
|
||||
├── data-model.md
|
||||
├── quickstart.md
|
||||
├── contracts/
|
||||
│ └── constitution-governance-contract.md
|
||||
└── tasks.md
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
|
||||
```text
|
||||
.specify/
|
||||
├── memory/
|
||||
│ └── constitution.md
|
||||
└── templates/
|
||||
└── checklist-template.md # referenced only; enforcement edits remain deferred to Spec 201
|
||||
|
||||
docs/
|
||||
└── ui/
|
||||
└── operator-ux-surface-standards.md
|
||||
|
||||
specs/
|
||||
├── 196-hard-filament-nativity-cleanup/
|
||||
├── 197-shared-detail-contract/
|
||||
├── 198-monitoring-page-state/
|
||||
├── 199-global-context-shell-contract/
|
||||
└── 200-filament-surface-rules/
|
||||
```
|
||||
|
||||
**Structure Decision**: This is a docs-only governance feature. The implementation centers on `.specify/memory/constitution.md` plus the feature artifacts in `specs/200-filament-surface-rules/`. `docs/ui/operator-ux-surface-standards.md` is a reference alignment target only if wording drift is discovered during implementation. Checklist or CI operationalization remains explicitly deferred to Spec 201.
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||
|-----------|------------|-------------------------------------|
|
||||
| None | N/A | N/A |
|
||||
|
||||
## Proportionality Review
|
||||
|
||||
- **Current operator problem**: Reviewers lack one stable language for classifying fake-native drift, bounded custom surfaces, shared-family host drift, and shell/page/detail state ownership. That makes future UI work inconsistent even when local specs identify specific defects.
|
||||
- **Existing structure is insufficient because**: The existing constitution has strong surface and action rules, but it does not yet state native-by-default, fake-native, shared-family, and explicit state-layer ownership as one integrated set grounded in the proven repo cases from Specs 196 through 199.
|
||||
- **Narrowest correct implementation**: Amend the existing constitution sections in place, add the minimal new vocabulary and anti-pattern catalog, define the exception boundary, and leave template/checklist/grep/test operationalization to Spec 201.
|
||||
- **Ownership cost created**: The repo gains a tighter review vocabulary and more precise constitutional language that maintainers must preserve in future UI specs and PRs.
|
||||
- **Alternative intentionally rejected**: A separate standalone UI constitution document, broad template enforcement in this spec, or a generic design-system framework detached from the existing constitution.
|
||||
- **Release truth**: current-release review truth and governance clarity
|
||||
|
||||
## Phase 0 — Outline & Research (Complete)
|
||||
|
||||
Outputs:
|
||||
- `specs/200-filament-surface-rules/research.md`
|
||||
|
||||
Unknowns resolved:
|
||||
- Which existing constitution sections should absorb the new rules so the repo avoids a parallel rulebook.
|
||||
- Which parts of Spec 200 are genuinely new versus clarifications or extensions of existing rule IDs.
|
||||
- How to model a contracts artifact for a docs-only governance slice without inventing runtime API endpoints.
|
||||
- Which adjacent documentation artifacts should be referenced for operator-language and progressive-disclosure alignment.
|
||||
- Which parts of review/checklist operationalization must stay deferred to Spec 201 to honor scope.
|
||||
|
||||
## Phase 1 — Design & Contracts (Complete)
|
||||
|
||||
Outputs:
|
||||
- `specs/200-filament-surface-rules/data-model.md`
|
||||
- `specs/200-filament-surface-rules/contracts/constitution-governance-contract.md`
|
||||
- `specs/200-filament-surface-rules/quickstart.md`
|
||||
|
||||
Design highlights:
|
||||
- The feature uses a conceptual data model only: amendment targets, vocabulary terms, anti-patterns, exception types, state-ownership rules, and cross-spec mappings.
|
||||
- No runtime API contract is introduced; the contract artifact explicitly records the docs-only governance scope, amendment targets, acceptance conditions, and deferred enforcement boundary.
|
||||
- The quickstart centers on amending existing constitution sections, validating representative cases, and leaving automation to Spec 201.
|
||||
|
||||
## Constitution Re-check (Post-Design)
|
||||
|
||||
Result: PASS
|
||||
|
||||
- The design remains docs-only and introduces no Graph calls, authorization changes, runtime state, or `OperationRun` behavior.
|
||||
- The proportionality trigger stays justified because the new taxonomy is bounded to proven repo problem classes and integrated into existing rule IDs.
|
||||
- No template, checklist, grep, lint, or runtime enforcement is pulled into this spec; those concerns remain explicitly deferred to Spec 201.
|
||||
- Legitimate custom surfaces remain available through the documented exception path; the design does not collapse all operator-facing UI into one Filament-only rule.
|
||||
|
||||
## Implementation Sequencing
|
||||
|
||||
1. Amend `.specify/memory/constitution.md` in place, targeting the existing sections `UI-FIL-001`, `UI-SURF-001`, `ACTSURF-001`, `HDR-001`, `UI-HARD-001`, `UI-EX-001`, `UI-REVIEW-001`, `Filament UI — Action Surface Contract`, and `UX-001` with the native-by-default, fake-native, shared-family, state-layer, reviewer-guidance, and exception rules.
|
||||
2. Add the glossary and anti-pattern catalog inside the amended constitution language so the new vocabulary is reviewable without creating a second rule source.
|
||||
3. Add the cross-spec mapping and close-out note in the Spec 200 artifact set, showing how Specs 196 through 199 feed the rules and what remains reserved for Spec 201.
|
||||
4. Validate the amended constitution against the representative cases from Specs 196 through 199 to confirm that each case can be classified through the new rule language alone.
|
||||
5. If wording drift is discovered, align `docs/ui/operator-ux-surface-standards.md` with the amended constitution without creating a competing standard or adding enforcement mechanics.
|
||||
6. Record explicit deferrals to Spec 201 for review checklist changes, grep/lint guards, test enforcement, or CI automation.
|
||||
89
specs/200-filament-surface-rules/quickstart.md
Normal file
89
specs/200-filament-surface-rules/quickstart.md
Normal file
@ -0,0 +1,89 @@
|
||||
# Quickstart: Implementing Spec 200
|
||||
|
||||
## Purpose
|
||||
|
||||
Use this sequence when turning Spec 200 into the actual constitution amendment.
|
||||
|
||||
This is a docs-only governance feature. Do not add runtime behavior, transport contracts, CI rules, or enforcement automation here.
|
||||
|
||||
## Inputs
|
||||
|
||||
- `specs/200-filament-surface-rules/spec.md`
|
||||
- `specs/200-filament-surface-rules/research.md`
|
||||
- `specs/200-filament-surface-rules/data-model.md`
|
||||
- `.specify/memory/constitution.md`
|
||||
- `docs/ui/operator-ux-surface-standards.md`
|
||||
- `specs/196-hard-filament-nativity-cleanup/spec.md`
|
||||
- `specs/197-shared-detail-contract/spec.md`
|
||||
- `specs/198-monitoring-page-state/spec.md`
|
||||
- `specs/199-global-context-shell-contract/spec.md`
|
||||
|
||||
## Steps
|
||||
|
||||
1. Start in `.specify/memory/constitution.md` and locate the existing sections targeted by the plan: `UI-SURF-001`, `ACTSURF-001`, `UI-HARD-001`, `UI-EX-001`, `Filament UI — Action Surface Contract`, `UX-001`, and `UI-FIL-001`.
|
||||
2. Add the minimum rule language needed to make native-by-default, fake-native, legitimate custom surfaces, shared detail families, and shell/page/detail state ownership explicit.
|
||||
3. Add the required vocabulary terms and anti-pattern names inside the amended constitution language instead of in a separate standalone document.
|
||||
4. Extend the exception model so legitimate deviations are named, bounded, and forced to state what remains standardized.
|
||||
5. Validate the amendment against the representative cases from Specs 196 through 199. Each case must be classifiable from the constitution text alone.
|
||||
6. Write the close-out note for Spec 200, clearly separating:
|
||||
- newly added or tightened constitution rules
|
||||
- clarifications to existing rules
|
||||
- items intentionally deferred to Spec 201
|
||||
7. Only if wording drift is discovered, align `docs/ui/operator-ux-surface-standards.md` with the amended constitution. Do not create a competing standards document.
|
||||
|
||||
## Representative Walkthrough
|
||||
|
||||
Use this exact walkthrough to validate that the amendment classifies the already proven repo cases without inventing new rule families during review.
|
||||
|
||||
| Source spec | Case to walk through | Constitution language that must classify it | Expected result |
|
||||
|---|---|---|---|
|
||||
| Spec 196 | Dependency edges, required-permissions filters, and evidence overview nativity cleanup | `UI-FIL-001`, `UI-HARD-001`, `UI-EX-001`, `UI-REVIEW-001` | Fake-native and simple-overview drift are rejected unless a bounded custom or nativity exception is explicit |
|
||||
| Spec 197 | Verification report and normalized diff/settings hosts | `UI-SURF-001`, `ACTSURF-001`, `HDR-001`, `Filament UI — Action Surface Contract`, `UI-REVIEW-001` | Shared-family core and host-owned variation are distinguishable; host drift is rejectable |
|
||||
| Spec 198 | Monitoring and compare state ownership | `UI-HARD-001`, `UX-001`, `UI-REVIEW-001` | Requested, active, draft, inspect, and restorable state roles are classifiable without treating them as one blur |
|
||||
| Spec 199 | Global context bar, tenantless shell, fallback behavior | `ACTSURF-001`, `UX-001`, `UI-REVIEW-001` | Global context state is clearly shell-owned and not silently re-owned by a page or partial |
|
||||
|
||||
### Walkthrough output checklist
|
||||
|
||||
1. Name the surface class and decide whether the surface is `Native Surface`, `Custom Surface`, or a `Shared Detail Micro-UI`.
|
||||
2. Decide whether any named anti-pattern appears: `Filament Costume`, `Blade Request UI`, `Hand-Rolled Simple Overview`, `Hidden Exception`, `Host Drift`, `State Layer Collapse`, or `Parallel Inspect Worlds`.
|
||||
3. If the case is still allowed, identify the exact exception type and the standardized parts that remain intact.
|
||||
4. Name which layer owns the relevant truth: `Global Context State`, `Page State`, or `Detail State`.
|
||||
5. Name which state roles matter: `Requested`, `Active`, `Draft`, `Inspect`, or `Restorable`.
|
||||
6. Stop if the review needs a new term. Spec 200 is only complete when the constitution text already contains the needed category.
|
||||
|
||||
## Cross-Spec Mapping
|
||||
|
||||
| Input spec | What Spec 200 absorbs | What remains outside Spec 200 |
|
||||
|---|---|---|
|
||||
| `196-hard-filament-nativity-cleanup` | native-by-default language, fake-native prohibitions, simple-overview default-to-native rule | runtime cleanup work and regression tests |
|
||||
| `197-shared-detail-contract` | shared detail micro-UI and host/core vocabulary, host-drift review gates | runtime host consolidation and regression tests |
|
||||
| `198-monitoring-page-state` | shell/page/detail ownership vocabulary and explicit state-role language | runtime page-state contract implementation and regression tests |
|
||||
| `199-global-context-shell-contract` | workspace-first shell ownership vocabulary and shell/page separation | runtime shell resolution and fallback behavior |
|
||||
| `201-*` | no new concepts; only enforcement consumers | checklist, grep, lint, CI, and regression operationalization |
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- The constitution is still the single source of truth.
|
||||
- No new runtime route, API, database, or enforcement code has been introduced.
|
||||
- Legitimate custom surfaces are still possible through the exception model.
|
||||
- Fake-native drift is named clearly enough that reviewers can identify it quickly.
|
||||
- Shared detail family and state-layer rules are grounded in the already proven cases from Specs 197, 198, and 199.
|
||||
- The handoff to Spec 201 is explicit and does not require new conceptual categories.
|
||||
|
||||
## Close-out Summary
|
||||
|
||||
When the amendment is finished, leave one concise close-out note that separates:
|
||||
|
||||
- **New clauses and vocabulary**:
|
||||
native/custom/fake-native classification, shared detail micro-UI and host language, state-layer ownership terms, and the named anti-pattern catalog.
|
||||
- **Tightened existing clauses**:
|
||||
native-by-default expectations, one-primary-interaction-model discipline, shared-family host ownership, record-header discipline for embedded families, and explicit review questions.
|
||||
- **Deferred enforcement**:
|
||||
checklist operationalization, grep/lint guards, CI checks, and runtime or test-backed regression enforcement in Spec 201.
|
||||
|
||||
## Not In Scope Here
|
||||
|
||||
- changing `.specify/templates/checklist-template.md` for enforcement behavior
|
||||
- adding grep or lint guards
|
||||
- adding CI checks
|
||||
- inventing sample runtime endpoints or implementation code snippets to simulate enforcement
|
||||
85
specs/200-filament-surface-rules/research.md
Normal file
85
specs/200-filament-surface-rules/research.md
Normal file
@ -0,0 +1,85 @@
|
||||
# Research: UI/UX Constitution Extension: Filament Nativity & Custom Surface Rules
|
||||
|
||||
## Decision 1: Amend existing constitution sections instead of creating a parallel UI rulebook
|
||||
|
||||
- **Decision**: Integrate Spec 200 into the existing constitution sections `UI-SURF-001`, `ACTSURF-001`, `UI-HARD-001`, `UI-EX-001`, `Filament UI — Action Surface Contract`, and `UX-001`.
|
||||
- **Rationale**: The repo already has a binding UI constitution. Extending those sections keeps one authority path for surface taxonomy, action discipline, hard rules, exceptions, Filament-specific contracts, and layout or IA guidance. This directly satisfies the spec's requirement to avoid a parallel rule hierarchy.
|
||||
- **Alternatives considered**:
|
||||
- Create a new top-level “Filament Nativity Constitution” section: rejected because it would force reviewers to reconcile two overlapping rulebooks.
|
||||
- Keep the rules distributed only across Specs 196 through 199: rejected because reviewers still need one durable product-language source rather than four adjacent historical specs.
|
||||
|
||||
## Decision 2: Treat native-by-default and fake-native as explicit extensions of existing UI-FIL-001 and UI-HARD-001 behavior
|
||||
|
||||
- **Decision**: Add an explicit native-by-default rule and a named fake-native anti-pattern to the existing Filament-native and hard-rule sections instead of inventing new standalone IDs.
|
||||
- **Rationale**: `UI-FIL-001` already says native Filament components come first, but it does not yet give reviewers a sharp fake-native vocabulary or clearly outlaw GET-form/request-driven body state for standard interactions. Spec 196 proved that gap.
|
||||
- **Alternatives considered**:
|
||||
- Leave native-by-default implicit in existing Filament guidance: rejected because implicit guidance did not prevent the repo's fake-native drift cases.
|
||||
- Introduce a new `UI-FIL-NATIVE-001` rule ID: rejected because the problem belongs inside the existing Filament-native section and hard-rule family.
|
||||
|
||||
## Decision 3: Model shared detail micro-UI and host variation as an extension of the current action-surface and exception framework
|
||||
|
||||
- **Decision**: Add shared-family and host-variation rules beneath the existing Filament UI contract and exception model instead of describing them as an unrelated custom-UI system.
|
||||
- **Rationale**: Spec 197 proved that the repeated problem is not “custom UI is bad,” but that repeated detail surfaces need one shared contract before host-specific variation is allowed. The existing action-surface and exception sections already govern how surfaces, actions, and deviations are classified.
|
||||
- **Alternatives considered**:
|
||||
- Create a separate “shared micro-UI framework” document: rejected because it would overproduce structure for two proven families and separate the rule from the main constitution.
|
||||
- Treat each host difference as a local PR concern: rejected because that is what allowed host drift to accumulate in the first place.
|
||||
|
||||
## Decision 4: Reuse the state-language proven in Specs 198 and 199 rather than invent a new runtime state taxonomy
|
||||
|
||||
- **Decision**: Carry forward shell/page/detail ownership and requested/active/draft/inspect/restorable distinctions as constitution vocabulary only, with no new runtime state framework in this spec.
|
||||
- **Rationale**: Specs 198 and 199 already proved the important state distinctions. Spec 200's job is to make those distinctions reviewable across future UI work, not to create a new implementation layer.
|
||||
- **Alternatives considered**:
|
||||
- Invent a fresh cross-product state taxonomy here: rejected because the repo already has adjacent specs proving the needed terms.
|
||||
- Limit the spec to visual nativity only: rejected because state-layer collapse is one of the core drift classes this spec must cover.
|
||||
|
||||
## Decision 5: Extend the existing exception model instead of normalizing hidden exceptions
|
||||
|
||||
- **Decision**: Keep `UI-EX-001` as the home for bounded exceptions and extend it with Spec 200's legitimate custom-surface and host-variation needs.
|
||||
- **Rationale**: The repo already recognizes that exceptions must be named, justified, and tested. Spec 200 broadens that discipline to fake-native escapes, legitimate custom surfaces, shared-family host variation, and state-related special cases without weakening the existing exception posture.
|
||||
- **Alternatives considered**:
|
||||
- Let individual specs define their own exception vocabulary: rejected because that recreates local drift.
|
||||
- Ban all custom surfaces to avoid exceptions entirely: rejected because the product legitimately includes richer diagnostic, review, and visualization surfaces.
|
||||
|
||||
## Decision 6: Treat the contracts artifact as an explicit no-runtime governance contract
|
||||
|
||||
- **Decision**: Create a docs-only contract note under `contracts/` that records amendment targets, acceptance conditions, and deferrals instead of inventing REST or GraphQL endpoints.
|
||||
- **Rationale**: Spec 200 introduces no user-facing API, route, or transport contract. The contract surface is the constitution amendment itself, so the artifact should make that boundary explicit rather than fabricate runtime endpoints that the spec forbids.
|
||||
- **Alternatives considered**:
|
||||
- Create a fake OpenAPI file: rejected because it would imply runtime behavior the feature does not add.
|
||||
- Omit `contracts/` entirely: rejected because the planning workflow expects an artifact and the no-runtime boundary should be made explicit.
|
||||
|
||||
## Decision 7: Reference operator-UX standards for language and disclosure, but defer review-checklist operationalization to Spec 201
|
||||
|
||||
- **Decision**: Use `docs/ui/operator-ux-surface-standards.md` as the supporting operator-language and progressive-disclosure reference, but keep checklist-template and enforcement work out of Spec 200 unless a wording-only cross-reference becomes unavoidable.
|
||||
- **Rationale**: Spec 200 is the rule and vocabulary slice. Spec 201 is the enforcement slice. That mirrors the repo's existing TEST-GOV-001 pattern, where standing governance rules live in the constitution and later specs operationalize them in templates and CI.
|
||||
- **Alternatives considered**:
|
||||
- Update review checklists now: rejected because it would blur the line between constitution definition and enforcement.
|
||||
- Ignore operator-UX standards docs completely: rejected because the constitution language should remain aligned with the repo's normative operator-facing UI guidance.
|
||||
|
||||
## Decision 8: Keep the implementation footprint intentionally small
|
||||
|
||||
- **Decision**: Plan for constitution changes in `.specify/memory/constitution.md`, feature-local artifacts in `specs/200-filament-surface-rules/`, and at most wording alignment in `docs/ui/operator-ux-surface-standards.md`.
|
||||
- **Rationale**: This keeps the scope proportional to a docs-only governance feature and avoids importing template, CI, or application-code changes that the spec explicitly defers.
|
||||
- **Alternatives considered**:
|
||||
- Widen the plan to include `.specify/templates/checklist-template.md`: rejected because template operationalization is enforcement work for Spec 201.
|
||||
- Widen the plan to include runtime examples in `app/` or `tests/`: rejected because Spec 200 is not an implementation spec.
|
||||
|
||||
## Decision 9: Put the new vocabulary into the existing rule families and appendices instead of creating a separate glossary section
|
||||
|
||||
- **Decision**: Introduce the new terms and anti-patterns inside the already binding rule families (`UI-SURF-001`, `UI-HARD-001`, `UI-EX-001`, `UX-001`, `UI-REVIEW-001`, `UI-FIL-001`) plus the condensed appendix/checklist/red-flag appendices, rather than creating a standalone vocabulary chapter.
|
||||
- **Rationale**: Reviewers need the terms exactly where they classify behavior. A standalone glossary would recreate the split-rulebook problem this spec is supposed to remove.
|
||||
- **Alternatives considered**:
|
||||
- Add a new glossary-only top-level constitution section: rejected because the review language would become detached from the operative rules.
|
||||
- Keep the vocabulary only in the Spec 200 artifact set: rejected because the constitution would still be missing the durable review language.
|
||||
|
||||
## Decision 10: Review questions should absorb nativity, shared-family, and state-layer checks directly
|
||||
|
||||
- **Decision**: Expand `UI-REVIEW-001`, Appendix B, and Appendix C with the new classification and anti-pattern checks rather than invent a second reviewer rubric for Filament nativity or state ownership.
|
||||
- **Rationale**: The existing enforcement model and appendices are already the review intake for operator-facing UI changes. Extending that surface is the narrowest way to make the new rules usable.
|
||||
- **Alternatives considered**:
|
||||
- Create a dedicated nativity/state checklist: rejected because it would fragment one review routine into multiple parallel rubrics.
|
||||
- Leave the new language implicit and rely on reviewer judgment: rejected because that is the current failure mode.
|
||||
|
||||
## Implementation Adjustment Note
|
||||
|
||||
- During implementation, the repo-level operator UX standards document only needs wording alignment that points back to the constitution vocabulary. A second standards track for native/custom/shared/state terminology would be overproduction and is intentionally avoided.
|
||||
314
specs/200-filament-surface-rules/spec.md
Normal file
314
specs/200-filament-surface-rules/spec.md
Normal file
@ -0,0 +1,314 @@
|
||||
# Feature Specification: UI/UX Constitution Extension: Filament Nativity & Custom Surface Rules
|
||||
|
||||
**Feature Branch**: `[200-filament-surface-rules]`
|
||||
**Created**: 2026-04-18
|
||||
**Status**: Proposed
|
||||
**Input**: User description: "Spec 200 - extend the UI/UX constitution with Filament nativity rules, custom surface allowance, shared detail micro-UI rules, state layering rules, and a documented exception model."
|
||||
|
||||
## Spec Candidate Check *(mandatory - SPEC-GATE-001)*
|
||||
|
||||
- **Problem**: The product still lacks one explicit constitution that answers when an admin surface must stay Filament-native, when a custom surface is legitimate, how repeated detail micro-UIs should be cut, and where shell, page, and detail state belong.
|
||||
- **Today's failure**: Reviewers and implementers can spot local defects, but they still lack one shared rule language for deciding whether a surface is fake-native drift, a bounded custom exception, host drift inside a shared family, or state-layer collapse.
|
||||
- **User-visible improvement**: Operators get more consistent, predictable admin UX over time because future surface work is reviewed against one product rule set instead of personal style, delivery pressure, or historical accident.
|
||||
- **Smallest enterprise-capable version**: Extend the existing UI constitution with bounded rules and vocabulary grounded in Specs 196 through 199, document the exception model, and define the handoff to Spec 201. Do not absorb cleanup sweeps, runtime refactors, or enforcement automation.
|
||||
- **Explicit non-goals**: No repo-wide cleanup pass, no CI or lint enforcement, no new runtime architecture, no new product surfaces, no separate design-system handbook, no page-state or shell-state implementation rewrite, and no attempt to forbid all custom surfaces.
|
||||
- **Permanent complexity imported**: A bounded review vocabulary, additional constitution clauses, an explicit anti-pattern catalog, a documented exception model, and a mapping from Specs 196 through 199 into one enduring rule set that Spec 201 can later enforce.
|
||||
- **Why now**: Specs 196 through 199 already exposed the repeated problem classes, and Spec 201 cannot operationalize guardrails cleanly unless those rules are first named, bounded, and integrated into the existing constitution.
|
||||
- **Why not local**: Local cleanup specs fix instances, but they do not settle the recurring review questions that keep resurfacing across adjacent UI work and future operator-facing surfaces.
|
||||
- **Approval class**: Core Enterprise
|
||||
- **Red flags triggered**: Two red flags are present: foundation-sounding constitution work and new cross-surface vocabulary. Defense: the scope is tightly bounded to already proven repo problem classes, integrates into existing constitution sections instead of inventing a parallel system, and explicitly defers runtime enforcement and automation to Spec 201.
|
||||
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 1 | Komplexität: 1 | Produktnähe: 2 | Wiederverwendung: 2 | **Gesamt: 10/12**
|
||||
- **Decision**: approve
|
||||
|
||||
## Spec Scope Fields *(mandatory)*
|
||||
|
||||
- **Scope**: canonical-view
|
||||
- **Primary Routes**: No new runtime route is introduced. The governed surfaces are the existing operator-facing admin surfaces under `/admin/**` and `/admin/t/{tenant}/...`, plus the workspace-owned constitution artifact that defines how those surfaces must be reviewed and extended.
|
||||
- **Data Ownership**: Workspace-owned constitution text, review vocabulary, anti-pattern definitions, and handoff notes only. No tenant-owned records, runtime tables, or persisted product entities are introduced.
|
||||
- **RBAC**: This feature does not change product authorization behavior. Future implementing work remains bound to the existing workspace membership, tenant entitlement, capability, and deny-as-not-found rules already enforced elsewhere in the product.
|
||||
|
||||
For canonical-view specs, the spec MUST define:
|
||||
|
||||
- **Default filter behavior when tenant-context is active**: This feature defines no new page or route filter behavior. It requires future specs and reviews to state explicitly whether tenant-context is shell scope, page prefilter, detail context, or unsupported state, and to keep those layers separate.
|
||||
- **Explicit entitlement checks preventing cross-tenant leakage**: This feature introduces no new access path. Any future application of these rules must preserve the existing workspace and tenant boundaries and must not let shell, page, or detail state silently broaden tenant scope.
|
||||
|
||||
## Proportionality Review *(mandatory when structural complexity is introduced)*
|
||||
|
||||
- **New source of truth?**: yes, but only as additional clauses inside the existing workspace-owned constitution rather than a new product runtime truth
|
||||
- **New persisted entity/table/artifact?**: yes, but only repository-owned artifacts such as constitution text, mapping notes, and checklist-level governance content
|
||||
- **New abstraction?**: yes, a bounded review vocabulary and exception model for UI surface classification
|
||||
- **New enum/state/reason family?**: yes, because the feature formalizes surface classes, state-layer distinctions, and named anti-pattern categories
|
||||
- **New cross-domain UI framework/taxonomy?**: yes, but only a bounded constitution taxonomy for already proven Filament-adjacent surface problems
|
||||
- **Current operator problem**: Surface quality still depends too much on who built the page and under what pressure. The same repo now contains fake-native controls, request-driven page bodies, shared-family drift, and mixed shell or page state with no single rule language that reviewers can apply consistently.
|
||||
- **Existing structure is insufficient because**: Specs 196 through 199 each proved one part of the problem, but without a shared constitution extension the repo still lacks one stable answer to whether a surface should be native, custom, shared-family, shell-owned, page-owned, or a documented exception.
|
||||
- **Narrowest correct implementation**: Extend the existing UI constitution sections with explicit definitions, native-by-default rules, custom-surface allowance, shared-family rules, state-layer rules, anti-patterns, and an exception model grounded in existing repo cases. Do not add runtime machinery, a new design system, or enforcement automation.
|
||||
- **Ownership cost**: Maintainers must preserve the vocabulary, keep the amended constitution coherent with future UI work, and ensure later specs and PRs use the same rule language instead of rephrasing it ad hoc.
|
||||
- **Alternative intentionally rejected**: Leave the topic split across adjacent specs, document local exceptions only in PRs, or create a separate parallel UI-rule document detached from the existing constitution.
|
||||
- **Release truth**: current-release review truth needed now to keep future UI work and Spec 201 aligned
|
||||
|
||||
## Problem Statement
|
||||
|
||||
The repo audit did not show that the product is fundamentally misbuilt. It showed that UI and surface quality still depends too much on local authorship and delivery pressure.
|
||||
|
||||
The repeated drift patterns are already visible:
|
||||
|
||||
- plain HTML controls wearing Filament styling
|
||||
- Blade-body micro-UIs with their own interaction contract
|
||||
- shared detail families growing by include or fork instead of by one contract
|
||||
- page-level state that is sometimes local, sometimes query-driven, and sometimes hybrid without a declared hierarchy
|
||||
- shell or context logic that still leaks into partials instead of one explicit resolver contract
|
||||
- legitimate custom surfaces that remain undocumented exceptions and therefore look indistinguishable from accidental drift
|
||||
|
||||
The missing ingredient is not another local cleanup. It is a constitution extension that answers the recurring product questions directly:
|
||||
|
||||
- What must be native?
|
||||
- When is custom legitimate?
|
||||
- How is a special case justified?
|
||||
- Which state belongs to shell, page, or detail?
|
||||
- How does a reviewer identify a regression into the old drift patterns early?
|
||||
|
||||
Spec 200 exists to establish those answers as durable product rules.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Depends on Spec 196 - Hard Filament Nativity Cleanup for the proven fake-native and request-driven UI cases that motivate the native-by-default rules.
|
||||
- Depends on Spec 197 - Shared Detail Micro-UI Contract for the proven repeated-host problem and the need for shared-core versus host-variation rules.
|
||||
- Depends on Spec 198 - Monitoring Page-State Contract for the proven page-state taxonomy and the need to distinguish requested, active, draft, inspect, and restorable state.
|
||||
- Depends on Spec 199 - Global Context Shell Contract for the proven workspace-first shell truth and the need to separate shell context from page and detail state.
|
||||
- Feeds Spec 201 - Enforcement and Guardrails, which will operationalize the rules from this spec in review, grep, lint, tests, or other automation.
|
||||
- Does not absorb cleanup implementation, runtime state rewrites, or enforcement machinery from any adjacent spec.
|
||||
|
||||
## Goals
|
||||
|
||||
- Make it explicit when a surface must use Filament-native primitives.
|
||||
- Make it explicit when a custom surface is legitimate and how narrow that exception must stay.
|
||||
- Define how shared detail micro-UI families are cut into shared contract versus host-specific variation.
|
||||
- Define how shell, page, and detail state are separated and reviewed.
|
||||
- Establish a named anti-pattern catalog so reviews describe the same problem classes with the same words.
|
||||
- Give Spec 201 a stable rule set to enforce instead of asking enforcement work to invent the rules later.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Cleaning up all existing violations immediately.
|
||||
- Enabling CI, lint, grep, or test enforcement in this spec.
|
||||
- Reorganizing navigation or inventing new product surfaces.
|
||||
- Creating a generic Filament theory document detached from repo reality.
|
||||
- Reimplementing page-state or shell-state runtime behavior beyond what adjacent specs already cover.
|
||||
- Prohibiting legitimate custom visualizations, diagnostic viewers, or multi-zone evidence surfaces.
|
||||
|
||||
## Assumptions
|
||||
|
||||
- The existing UI constitution sections, especially UI-CONST-001, DECIDE-001, UI-EX-001, UI-REVIEW-001, HDR-001, and UI-FIL-001, remain the binding base that this spec extends rather than replaces.
|
||||
- Filament-native admin surfaces remain the normal product shell for standard forms, filters, tables, badges, tabs, and actions.
|
||||
- Legitimate custom surfaces will continue to exist for richer visualization, diagnostics, diff, review, and embedded evidence work.
|
||||
- Enforcement is intentionally deferred to Spec 201, not partially implemented here.
|
||||
- Review usefulness matters more than architectural purity; if a rule does not help a reviewer classify a real repo case, it is too abstract.
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- **Native by default**: Standard form, filter, table, action, and overview work belongs on native Filament or Livewire primitives when an appropriate primitive already exists.
|
||||
- **Fake-native is a first-class violation**: A surface that only looks native, but keeps a separate HTML, GET, or Blade-request contract for the same core interaction, is drift.
|
||||
- **Custom requires product reason**: Custom surfaces are legitimate only when the product need is genuinely richer than standard CRUD or overview semantics and the exception is bounded explicitly.
|
||||
- **Shared contract before host fork**: Repeated detail families must be described once as a shared family before host-specific variation is allowed.
|
||||
- **State belongs to the right layer**: Workspace and tenant truth belong to the shell, page interaction belongs to the page, and viewer micro-state belongs to the detail surface.
|
||||
- **One primary interaction model per concern**: Surfaces may not run two competing inspect, tab, filter, or selection contracts for the same user concern.
|
||||
- **Explicit exceptions beat implicit drift**: Legitimate special cases must be named, bounded, and reviewable instead of surviving through historical accident.
|
||||
|
||||
## Required Outcomes
|
||||
|
||||
### Constitution Integration
|
||||
|
||||
The existing constitution must gain explicit rule language for Filament nativity, fake-native prohibitions, custom-surface allowance, shared detail micro-UI families, shell or page or detail state layering, and exception handling without creating a second parallel rulebook.
|
||||
|
||||
### Shared Review Vocabulary
|
||||
|
||||
The product must gain one stable vocabulary for the following concepts:
|
||||
|
||||
- Native Surface
|
||||
- Fake-Native Surface
|
||||
- Custom Surface
|
||||
- Shared Detail Micro-UI
|
||||
- Host
|
||||
- Global Context State
|
||||
- Page State
|
||||
- Detail State
|
||||
- Legitimate Exception
|
||||
|
||||
### Anti-Pattern Catalog
|
||||
|
||||
The constitution must catalog the recurring failure modes that the repo audit already exposed, so reviewers can call them by name and reject them consistently.
|
||||
|
||||
### Exception Model
|
||||
|
||||
The constitution must define how a legitimate deviation from native-by-default or shared-contract rules is justified, bounded, and kept from turning into a general permission slip.
|
||||
|
||||
### Cross-Spec Mapping
|
||||
|
||||
The constitution extension must explain how Specs 196 through 199 feed the new rules and which part of the work remains reserved for Spec 201.
|
||||
|
||||
### Close-Out Note
|
||||
|
||||
The finished spec must leave one explicit summary of what constitution rules were added or amended, which existing rules were tightened or clarified, and which topics remain enforcement follow-up rather than implementation scope.
|
||||
|
||||
## Representative Validation Cases
|
||||
|
||||
- **Dependency edges detail surface**: A reviewer must be able to classify a dependency-edge surface as a native-surface violation when standard controls are replaced by GET-form or Blade-request state without a true product reason.
|
||||
- **Verification report host family**: A reviewer must be able to classify host-specific structural drift inside repeated verification-report surfaces as a shared-family violation unless a clearly justified subtype exists.
|
||||
- **Monitoring page inspect and filter state**: A reviewer must be able to classify mixed requested, active, draft, or inspect state as page-state drift rather than as a harmless local implementation detail.
|
||||
- **Global context shell behavior**: A reviewer must be able to classify shell partials that quietly own scope truth or remembered context as shell-contract violations rather than neutral presentation code.
|
||||
- **Legitimate special visualization**: A reviewer must still be able to approve a truly custom visualization or rich diagnostic viewer when the product reason is real and the exception remains narrow and explicit.
|
||||
|
||||
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
|
||||
|
||||
- **Test purpose / classification**: N/A
|
||||
- **Validation lane(s)**: N/A
|
||||
- **Why this classification and these lanes are sufficient**: This feature changes repository governance text and specification artifacts, not product runtime behavior. Validation is document-based and depends on completeness, clarity, and traceability to real repo cases.
|
||||
- **New or expanded test families**: none
|
||||
- **Fixture / helper cost impact**: none
|
||||
- **Heavy-family visibility / justification**: none
|
||||
- **Reviewer handoff**: Reviewers must confirm that the new rules are grounded in Specs 196 through 199, that no enforcement or runtime behavior is claimed without a follow-up implementation spec, that no parallel rulebook is created, and that the resulting vocabulary is sufficient to classify the representative cases above.
|
||||
- **Budget / baseline / trend impact**: none
|
||||
- **Escalation needed**: none
|
||||
- **Planned validation commands**: N/A
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - Classify Nativity And Exceptions Consistently (Priority: P1)
|
||||
|
||||
As a reviewer, I want one constitution vocabulary for native, fake-native, and legitimate exception decisions so I can classify a surface without inventing local review language every time.
|
||||
|
||||
**Why this priority**: The main current pain is review inconsistency. If the spec cannot standardize the classification language, the rest of the rule set will remain too soft to matter.
|
||||
|
||||
**Independent Test**: Can be fully tested by reviewing a representative fake-native case and a representative legitimate custom case and verifying that both can be classified through the constitution alone.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a Filament-looking surface that still uses plain HTML controls and request-driven body state, **When** a reviewer applies the amended constitution, **Then** the reviewer can classify it as fake-native drift without adding new terminology.
|
||||
2. **Given** a rich diagnostic or visualization surface that does not fit standard CRUD or overview primitives, **When** a reviewer applies the amended constitution, **Then** the reviewer can approve it only through the documented exception model and product reason.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - Cut Shared Detail Families And State Layers Correctly (Priority: P1)
|
||||
|
||||
As an author or reviewer of operator-facing UI, I want one rule set for shared detail families and shell, page, and detail state so that repeated hosts and complex monitoring pages do not quietly re-fork the product contract.
|
||||
|
||||
**Why this priority**: Shared-family drift and state-layer collapse were both already proven by adjacent specs and are the largest remaining sources of UI inconsistency after obvious fake-native cleanup.
|
||||
|
||||
**Independent Test**: Can be fully tested by evaluating one shared detail family and one monitoring or shell-context case and confirming that the constitution states where the shared contract ends, where host variation begins, and where each state class belongs.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** the same detail micro-UI appears in multiple hosts, **When** the reviewer applies the amended constitution, **Then** the reviewer can decide whether the hosts share one family contract or have drifted into unapproved forks.
|
||||
2. **Given** a page mixes query state, local state, inspect state, and shell context, **When** the reviewer applies the amended constitution, **Then** the reviewer can identify which state belongs to shell, page, or detail instead of treating the mixture as harmless implementation detail.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - Prepare Enforcement Without Re-Inventing Rules (Priority: P2)
|
||||
|
||||
As the author of follow-up guardrails, I want the constitution to state the reviewable rule classes explicitly so that enforcement can be derived from stable product language rather than from one-off heuristics.
|
||||
|
||||
**Why this priority**: Spec 201 should operationalize this rule set, not rediscover it. Without an explicit handoff, enforcement work will either be too weak or will invent a different vocabulary.
|
||||
|
||||
**Independent Test**: Can be fully tested by mapping the amended constitution to a future guardrail backlog and verifying that the enforcement targets follow directly from the named rule and anti-pattern classes.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** the amended constitution is complete, **When** a maintainer defines follow-up enforcement targets, **Then** the maintainer can identify which rule classes are enforceable without needing new conceptual categories.
|
||||
2. **Given** a future UI change violates one of the named anti-patterns, **When** Spec 201 is planned, **Then** the follow-up spec can reference the same vocabulary and representative cases instead of restating the rule family from scratch.
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- A surface visually matches Filament but keeps its real behavior in GET forms, query parsing, or Blade-body request state.
|
||||
- A custom surface is genuinely justified but risks becoming a broad precedent for unrelated ad hoc markup.
|
||||
- A repeated detail surface appears in only one current host but is about to appear in another and must not be prematurely over-generalized.
|
||||
- Remembered shell context, requested page state, and local viewer state disagree on first load.
|
||||
- A historically grown special surface has no explicit exception record and must be classified as either an allowed exception to document or a drift case to reject.
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
**Constitution alignment (required):** This feature changes repository-owned constitution and review guidance only. It introduces no Microsoft Graph calls, no write workflow, no long-running job, and no new runtime route. Existing operator-facing pages remain governed by their current implementation and authorization contracts until follow-up specs or code changes adopt these rules.
|
||||
|
||||
**Constitution alignment (PROP-001 / ABSTR-001 / PERSIST-001 / STATE-001 / BLOAT-001):** This feature introduces a bounded new vocabulary and rule taxonomy inside the existing constitution. It does not add product persistence, runtime abstractions, or execution-state machinery. The proportionality review above explains why the new language is justified now and how the scope remains tighter than a generic UI framework.
|
||||
|
||||
**Constitution alignment (TEST-GOV-001):** This feature does not change runtime behavior or test families. Validation is document-based. Any future automation, linting, grep rules, or regression tests that enforce this constitution are deferred to Spec 201 and must carry their own lane and runtime impact disclosure.
|
||||
|
||||
**Constitution alignment (OPS-UX):** Not applicable. This feature creates no `OperationRun`, changes no run status transitions, and does not alter toast, progress, or terminal-notification rules.
|
||||
|
||||
**Constitution alignment (RBAC-UX):** This feature does not change authorization behavior. It reinforces that shell, page, and detail state rules must not bypass workspace membership, tenant entitlement, capability checks, or deny-as-not-found semantics in future implementations.
|
||||
|
||||
**Constitution alignment (OPS-EX-AUTH-001):** Not applicable. Authentication handshake behavior is unchanged.
|
||||
|
||||
**Constitution alignment (BADGE-001):** The feature must not create a new page-local badge or severity language. It extends existing UI rules so future surfaces know when native or shared semantics are required and when local status language is forbidden.
|
||||
|
||||
**Constitution alignment (UI-FIL-001):** This feature explicitly extends UI-FIL-001 by clarifying when Filament-native primitives are mandatory, which fake-native substitutes are prohibited, and when a product-grounded exception is legitimate.
|
||||
|
||||
**Constitution alignment (UI-NAMING-001):** The new vocabulary is review-facing and architectural, not operator-facing action copy. It must remain concise, stable, and tied to repeated repo problem classes rather than implementation jargon.
|
||||
|
||||
**Constitution alignment (DECIDE-001):** This feature does not create a new operator surface. It strengthens the review language that future specs and PRs must use when classifying surfaces, exceptions, and the human-in-the-loop role of a page.
|
||||
|
||||
**Constitution alignment (UI-CONST-001 / UI-SURF-001 / ACTSURF-001 / UI-HARD-001 / UI-EX-001 / UI-REVIEW-001 / HDR-001):** The feature must integrate into the existing UI constitution family, not sit beside it. Any new rule must either extend an existing section cleanly or add a narrowly scoped adjacent clause that still participates in the same review model.
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-200-001**: The product constitution MUST be extended inside the existing UI constitution structure rather than through a separate standalone rule document.
|
||||
- **FR-200-002**: The constitution MUST define the following terms explicitly: Native Surface, Fake-Native Surface, Custom Surface, Shared Detail Micro-UI, Host, Global Context State, Page State, Detail State, and Legitimate Exception.
|
||||
- **FR-200-003**: The constitution MUST state that standard form, filter, table, action, tab, badge, link, and overview work is native-by-default when suitable Filament or existing shared primitives are available.
|
||||
- **FR-200-004**: The constitution MUST prohibit plain HTML or Filament-costume markup as the primary interaction contract for standard controls when native primitives fit the job.
|
||||
- **FR-200-005**: The constitution MUST classify GET forms and Blade `request()` body-state as forbidden fake-native patterns when they act as the primary interaction contract inside a running Filament surface, unless an explicit and bounded exception is documented.
|
||||
- **FR-200-006**: The constitution MUST state that simple report or overview surfaces with ordinary columns, filters, empty states, and navigation default to native table semantics unless a real product reason requires a custom surface.
|
||||
- **FR-200-007**: The constitution MUST define the legitimate reasons for a custom surface, including richer visualization, high-value diagnostic or review work, multi-zone shared detail micro-UIs, shell-context-specific UI, and domain presentation that does not fit standard CRUD.
|
||||
- **FR-200-008**: The constitution MUST require every legitimate custom surface to declare its state layers, expected inputs, host-owned actions, shared-core versus host-specific variation, and any shareable or restorable state that matters to the operator.
|
||||
- **FR-200-009**: The constitution MUST state that speed of implementation, historical growth, or local convenience are not sufficient reasons for a custom-surface or fake-native exception.
|
||||
- **FR-200-010**: The constitution MUST define shared detail family rules that require a common core contract, mandatory zones, optional zones, host extension points, and explicit state responsibilities before host-specific variation is allowed.
|
||||
- **FR-200-011**: The constitution MUST limit host variation to contextual framing, allowed actions, and approved optional zones, and MUST classify structural host forks for the same family as drift unless a new subtype is explicitly justified.
|
||||
- **FR-200-012**: The constitution MUST define page-state rules that distinguish initial requested state, active page state, optional draft state, inspect state, and shareable or restorable state, and MUST require each state class to declare any query or URL role explicitly.
|
||||
- **FR-200-013**: The constitution MUST define global shell rules that keep workspace as the primary context, tenant as the secondary context, remembered context as subordinate convenience state, and shell partials as renderers of the context contract rather than owners of it.
|
||||
- **FR-200-014**: The constitution MUST require one primary interaction model per concern and MUST classify competing inspect, tab, filter, or selection contracts on the same surface as a violation unless a documented exception defines their hierarchy.
|
||||
- **FR-200-015**: The constitution MUST define an explicit exception model that requires each deviation from native-by-default or shared-family rules to state why native or default behavior does not fit, why custom behavior is necessary, what remains standardized, and how the exception stays narrow.
|
||||
- **FR-200-016**: The constitution MUST catalog at least the following anti-patterns explicitly: Filament Costume, Blade Request UI, Hand-Rolled Simple Overview, Host Drift, State Layer Collapse, Parallel Inspect Worlds, and Hidden Exception.
|
||||
- **FR-200-017**: The constitution MUST add reviewer-facing guidance and questions that let maintainers classify representative cases from Specs 196 through 199 without creating new local vocabulary during review.
|
||||
- **FR-200-018**: The feature MUST document how Specs 196 through 199 feed the new rules and which part of the problem remains delegated to Spec 201 for enforcement.
|
||||
- **FR-200-019**: The feature MUST produce a close-out summary describing which constitution rules were added, which existing rules were tightened or clarified, and which future enforcement topics remain intentionally deferred.
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- **NFR-200-001**: The amended rules MUST stay grounded in already observed repo problem classes and be precise enough that a reviewer can classify the representative cases without supplementary verbal explanation.
|
||||
- **NFR-200-002**: The feature MUST remain docs-only and governance-only. It MUST NOT introduce runtime routes, provider registration changes, assets, persistence, or Graph behavior.
|
||||
- **NFR-200-003**: The resulting constitution MUST not accidentally ban all custom surfaces. Legitimate rich visualization, diagnostics, evidence, and multi-zone detail work must remain available through the documented exception path.
|
||||
- **NFR-200-004**: The extension MUST integrate into the existing constitution and review model rather than creating a second manual or rule hierarchy that reviewers must reconcile separately.
|
||||
- **NFR-200-005**: The wording MUST be strong enough for future enforcement work to derive guardrails from it, but not so dogmatic that it overrides real product reasons for bounded custom behavior.
|
||||
|
||||
### Key Entities *(include if feature involves data)*
|
||||
|
||||
- **Native Surface Classification**: The constitution-level classification for surfaces whose core interaction is carried by Filament-native or existing shared primitives without a competing local contract.
|
||||
- **Fake-Native Surface**: A surface that visually imitates native admin UI while keeping its actual interaction contract in plain HTML, GET forms, ad hoc request parsing, or other unnecessary local behavior.
|
||||
- **Custom Surface Allowance**: The bounded rule that permits truly product-specific surfaces when standard CRUD or overview primitives are not sufficient.
|
||||
- **Shared Detail Micro-UI Family**: A repeated domain detail surface that must define shared core zones, optional zones, and host variation before multiple hosts can evolve safely.
|
||||
- **State Layer Classification**: The distinction between global shell context, page interaction state, and detail micro-state, including how requested, active, draft, inspect, and restorable state are separated.
|
||||
- **Legitimate Exception Record**: The explicit rule-level justification that allows a bounded deviation from native-by-default or shared-family rules.
|
||||
- **Anti-Pattern Class**: A named review category for recurring UI drift that should be rejected or remediated consistently.
|
||||
|
||||
## Deliverables
|
||||
|
||||
- **D-200-001**: A constitution amendment that integrates the new Filament nativity, custom-surface, shared-family, state-layer, and exception rules into the existing UI constitution.
|
||||
- **D-200-002**: A stable vocabulary and anti-pattern catalog covering native, fake-native, custom, shared detail micro-UI, shell or page or detail state, and legitimate exception.
|
||||
- **D-200-003**: An explicit exception model for bounded custom surfaces and deliberate deviations from native-by-default rules.
|
||||
- **D-200-004**: A mapping note that explains how Specs 196 through 199 feed the new constitution rules and how Spec 201 will consume them for enforcement.
|
||||
- **D-200-005**: A close-out note that lists new clauses, amended clauses, and intentionally deferred enforcement follow-up.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-200-001**: The amended constitution explicitly defines all nine required vocabulary terms from FR-200-002 with no unresolved placeholder or clarification marker.
|
||||
- **SC-200-002**: During document review, a reviewer can classify one representative case from each of Specs 196, 197, 198, and 199 using only the amended constitution and the named anti-pattern or exception vocabulary.
|
||||
- **SC-200-003**: The amended constitution contains explicit rule language for native-by-default behavior, fake-native prohibitions, shared-family contracts, page-state layering, shell-context layering, and legitimate exceptions.
|
||||
- **SC-200-004**: The result integrates into the existing constitution model and does not create a parallel standalone rulebook for Filament-adjacent UI development.
|
||||
- **SC-200-005**: The handoff to Spec 201 identifies the enforceable rule classes without requiring Spec 201 to invent new conceptual categories.
|
||||
- **SC-200-006**: In acceptance review, a team member can explain when native is required, when custom is legitimate, how shared families are cut, and where state belongs after reading the amended constitution and this spec.
|
||||
|
||||
## Follow-up Assumptions
|
||||
|
||||
- Adjacent specs remain the authoritative evidence base for the specific repo cases this constitution extension names.
|
||||
- Future code changes will update the constitution and follow-up specs rather than treating this vocabulary as optional review prose.
|
||||
- Spec 201 will remain a separate enforcement slice instead of being partially absorbed into implementation PRs.
|
||||
174
specs/200-filament-surface-rules/tasks.md
Normal file
174
specs/200-filament-surface-rules/tasks.md
Normal file
@ -0,0 +1,174 @@
|
||||
# Tasks: UI/UX Constitution Extension: Filament Nativity & Custom Surface Rules
|
||||
|
||||
**Input**: Design documents from `/specs/200-filament-surface-rules/`
|
||||
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, quickstart.md, contracts/constitution-governance-contract.md
|
||||
|
||||
**Tests**: N/A. This is a docs-only governance feature; no runtime or test-surface changes are planned in Spec 200.
|
||||
|
||||
## Test Governance Checklist
|
||||
|
||||
N/A for this docs-only governance feature. Keep the feature bounded to documentation and constitution artifacts only.
|
||||
|
||||
## Phase 1: Setup (Shared Context)
|
||||
|
||||
**Purpose**: Lock the implementation scope, amendment targets, and supporting source material before editing the constitution.
|
||||
|
||||
- [x] T001 Review `specs/200-filament-surface-rules/spec.md`, `specs/200-filament-surface-rules/plan.md`, and `specs/200-filament-surface-rules/research.md` to lock the amendment scope and Spec 201 deferrals.
|
||||
- [x] T002 [P] Inspect `.specify/memory/constitution.md` and `docs/ui/operator-ux-surface-standards.md` to confirm the exact insertion points for `UI-FIL-001`, `UI-SURF-001`, `ACTSURF-001`, `HDR-001`, `UI-HARD-001`, `UI-EX-001`, `UI-REVIEW-001`, `Filament UI — Action Surface Contract`, and `UX-001`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Foundational (Blocking Prerequisites)
|
||||
|
||||
**Purpose**: Prepare the shared governance artifacts and validation scaffolding that all story work depends on.
|
||||
|
||||
**⚠️ CRITICAL**: No user story work should begin until this phase is complete.
|
||||
|
||||
- [x] T003 Align `specs/200-filament-surface-rules/contracts/constitution-governance-contract.md` with the agreed amendment targets, acceptance contract, and explicit no-runtime boundary before constitution edits begin.
|
||||
- [x] T004 [P] Normalize the baseline amendment-target, vocabulary, anti-pattern, exception, and state-ownership definitions in `specs/200-filament-surface-rules/data-model.md`.
|
||||
- [x] T005 [P] Expand `specs/200-filament-surface-rules/quickstart.md` with the baseline representative validation flow for Specs 196 through 199 and the explicit Spec 201 handoff.
|
||||
|
||||
**Checkpoint**: Amendment scope, supporting vocabulary, and deferred enforcement boundary are locked.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: User Story 1 - Classify Nativity And Exceptions Consistently (Priority: P1) 🎯 MVP
|
||||
|
||||
**Goal**: Make native-by-default, fake-native drift, and legitimate custom-surface exceptions reviewable through the constitution alone.
|
||||
|
||||
**Independent Test**: A reviewer can classify a fake-native dependency surface and a legitimate custom visualization using only the amended constitution and the representative cases from Spec 196.
|
||||
|
||||
- [x] T006 [US1] Amend `.specify/memory/constitution.md` in `UI-FIL-001` and `UI-HARD-001` to add explicit native-by-default language and fake-native prohibitions.
|
||||
- [x] T007 [US1] Amend `.specify/memory/constitution.md` in `UI-EX-001` to define bounded legitimate custom-surface and nativity-exception rules.
|
||||
- [x] T008 [US1] Add `Native Surface`, `Fake-Native Surface`, `Custom Surface`, `Legitimate Exception`, plus the `Filament Costume`, `Blade Request UI`, `Hand-Rolled Simple Overview`, and `Hidden Exception` catalog entries in `.specify/memory/constitution.md`.
|
||||
- [x] T009 [US1] Amend `.specify/memory/constitution.md` in `UI-REVIEW-001` to add reviewer-facing nativity and exception-classification questions, then validate the amended language against `specs/196-hard-filament-nativity-cleanup/spec.md` and record any wording refinements in `specs/200-filament-surface-rules/quickstart.md`.
|
||||
|
||||
**Checkpoint**: Native and exception cases are classifiable without local review vocabulary.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: User Story 2 - Cut Shared Detail Families And State Layers Correctly (Priority: P1)
|
||||
|
||||
**Goal**: Make shared detail micro-UI families, host variation, and shell/page/detail state ownership explicit in the constitution.
|
||||
|
||||
**Independent Test**: A reviewer can classify shared-family drift and shell/page/detail state confusion using only the amended constitution and the representative cases from Specs 197 through 199.
|
||||
|
||||
- [x] T010 [US2] Amend `.specify/memory/constitution.md` in `UI-SURF-001`, `ACTSURF-001`, `HDR-001`, and `Filament UI — Action Surface Contract` to define shared detail micro-UI families, one-primary-interaction-model guidance, shared core zones, bounded host variation, and any related header-discipline clarifications.
|
||||
- [x] T011 [US2] Amend `.specify/memory/constitution.md` in `UX-001` and adjacent UI sections to define shell/page/detail ownership plus requested, active, draft, inspect, and restorable state roles.
|
||||
- [x] T012 [US2] Add `Shared Detail Micro-UI`, `Host`, `Global Context State`, `Page State`, `Detail State`, `Host Drift`, `State Layer Collapse`, and `Parallel Inspect Worlds` entries in `.specify/memory/constitution.md`.
|
||||
- [x] T013 [US2] Amend `.specify/memory/constitution.md` in `UI-REVIEW-001`, `ACTSURF-001`, `HDR-001`, and `UI-HARD-001` review gates to add reviewer-facing shared-family and state-layer questions, then validate the amended language against `specs/197-shared-detail-contract/spec.md`, `specs/198-monitoring-page-state/spec.md`, and `specs/199-global-context-shell-contract/spec.md`, and record any refinements in `specs/200-filament-surface-rules/quickstart.md`.
|
||||
|
||||
**Checkpoint**: Shared-family and state-layer cases are classifiable without inventing a second taxonomy.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: User Story 3 - Prepare Enforcement Without Re-Inventing Rules (Priority: P2)
|
||||
|
||||
**Goal**: Leave a clean cross-spec mapping and enforcement handoff so Spec 201 can operationalize the rules directly.
|
||||
|
||||
**Independent Test**: A maintainer can derive Spec 201 enforcement targets from the amended constitution and feature artifacts without introducing new rule categories.
|
||||
|
||||
- [x] T014 [US3] Update `specs/200-filament-surface-rules/contracts/constitution-governance-contract.md` with the finalized cross-spec mapping and the deferred enforcement boundary for Spec 201.
|
||||
- [x] T015 [P] [US3] Update `specs/200-filament-surface-rules/data-model.md` with the finalized `CrossSpecMapping` and exception-type relationships produced by the amended constitution.
|
||||
- [x] T016 [P] [US3] Add the close-out summary of new clauses, tightened clauses, and intentionally deferred enforcement work to `specs/200-filament-surface-rules/quickstart.md`.
|
||||
- [x] T017 [US3] Review `.specify/memory/constitution.md`, `specs/200-filament-surface-rules/contracts/constitution-governance-contract.md`, and `specs/200-filament-surface-rules/quickstart.md` together to verify that Spec 201 can consume the final vocabulary directly.
|
||||
|
||||
**Checkpoint**: The constitution amendment and Spec 201 handoff are aligned and explicit.
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Polish & Cross-Cutting Concerns
|
||||
|
||||
**Purpose**: Final alignment, wording cleanup, and end-to-end validation across all stories.
|
||||
|
||||
- [x] T018 [P] Align any remaining wording drift between `.specify/memory/constitution.md` and `docs/ui/operator-ux-surface-standards.md`.
|
||||
- [x] T019 [P] Refresh `specs/200-filament-surface-rules/research.md` with any final amendment-target or rejected-alternative adjustments discovered during implementation.
|
||||
- [x] T020 Run the representative-case walkthrough in `specs/200-filament-surface-rules/quickstart.md` against `.specify/memory/constitution.md` plus `specs/196-hard-filament-nativity-cleanup/spec.md`, `specs/197-shared-detail-contract/spec.md`, `specs/198-monitoring-page-state/spec.md`, and `specs/199-global-context-shell-contract/spec.md`.
|
||||
- [x] T021 Clean up and cross-check `specs/200-filament-surface-rules/plan.md`, `specs/200-filament-surface-rules/contracts/constitution-governance-contract.md`, and `specs/200-filament-surface-rules/quickstart.md` so the final artifact set tells one consistent story.
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Execution Order
|
||||
|
||||
### Phase Dependencies
|
||||
|
||||
- **Setup (Phase 1)**: No dependencies.
|
||||
- **Foundational (Phase 2)**: Depends on Setup completion and blocks all story work.
|
||||
- **User Story 1 (Phase 3)**: Depends on Foundational completion.
|
||||
- **User Story 2 (Phase 4)**: Depends on Foundational completion.
|
||||
- **User Story 3 (Phase 5)**: Depends on User Stories 1 and 2 because the enforcement handoff must reflect the final amended rule language.
|
||||
- **Polish (Phase 6)**: Depends on all user stories being complete.
|
||||
|
||||
### User Story Dependencies
|
||||
|
||||
- **US1 (P1)**: Can start immediately after Foundational and serves as the MVP slice.
|
||||
- **US2 (P1)**: Can start after Foundational, but it edits the same constitution file as US1, so one implementer should do the core `.specify/memory/constitution.md` edits sequentially or split them by section ownership deliberately.
|
||||
- **US3 (P2)**: Starts after US1 and US2 stabilize because it documents the final mapping and Spec 201 handoff.
|
||||
|
||||
### Within Each User Story
|
||||
|
||||
- Core `.specify/memory/constitution.md` edits should be completed before validation tasks for that story.
|
||||
- Story validation tasks should feed any refinement updates into `specs/200-filament-surface-rules/quickstart.md`.
|
||||
- Close-out and handoff tasks should only run after the governing constitution language is stable.
|
||||
|
||||
### Parallel Opportunities
|
||||
|
||||
- `T002` can run in parallel with `T001` after the feature scope is understood.
|
||||
- `T004` and `T005` can run in parallel after `T003` establishes the final contract boundary.
|
||||
- `T015` and `T016` can run in parallel once the final constitution wording from US1 and US2 is stable.
|
||||
- `T018` and `T019` can run in parallel during Polish because they touch different files.
|
||||
|
||||
---
|
||||
|
||||
## Parallel Example: Foundational Work
|
||||
|
||||
```bash
|
||||
# After T003 locks the final contract boundary:
|
||||
Task: "Normalize the amendment-target, vocabulary, anti-pattern, exception, and state-ownership definitions in specs/200-filament-surface-rules/data-model.md"
|
||||
Task: "Expand specs/200-filament-surface-rules/quickstart.md with the representative validation flow for Specs 196 through 199 and the explicit Spec 201 handoff"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Parallel Example: User Story 3
|
||||
|
||||
```bash
|
||||
# After US1 and US2 stabilize the final constitution wording:
|
||||
Task: "Update specs/200-filament-surface-rules/data-model.md with the finalized CrossSpecMapping and exception-type relationships"
|
||||
Task: "Add the close-out summary of new clauses, tightened clauses, and intentionally deferred enforcement work to specs/200-filament-surface-rules/quickstart.md"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### MVP First (User Story 1 Only)
|
||||
|
||||
1. Complete Phase 1: Setup.
|
||||
2. Complete Phase 2: Foundational.
|
||||
3. Complete Phase 3: User Story 1.
|
||||
4. **STOP and VALIDATE**: Confirm a reviewer can classify a fake-native case and a legitimate custom case from the amended constitution alone.
|
||||
|
||||
### Incremental Delivery
|
||||
|
||||
1. Complete Setup + Foundational.
|
||||
2. Add User Story 1 and validate nativity/exception classification.
|
||||
3. Add User Story 2 and validate shared-family/state-layer classification.
|
||||
4. Add User Story 3 and validate the Spec 201 handoff.
|
||||
5. Finish with Polish and representative-case walkthrough.
|
||||
|
||||
### Parallel Team Strategy
|
||||
|
||||
With multiple contributors:
|
||||
|
||||
1. One contributor completes the contract-boundary task `T003`.
|
||||
2. A second contributor can prepare `T004` while another prepares `T005`.
|
||||
3. Once the constitution wording stabilizes, one contributor can finalize the contract note while another finalizes the close-out summary and data-model mapping.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- `[P]` tasks are parallelizable because they touch different files and do not depend on unfinished work in the same artifact.
|
||||
- User-story phases remain independently valuable, but US1 and US2 share `.specify/memory/constitution.md`, so section-level coordination matters.
|
||||
- No runtime tests, CI rules, or lint/grep enforcement should be added under this task list; those remain reserved for Spec 201.
|
||||
@ -0,0 +1,37 @@
|
||||
# Specification Quality Checklist: Enforcement & Review Guardrails
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2026-04-18
|
||||
**Feature**: [spec.md](../spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [x] No implementation details (languages, frameworks, APIs)
|
||||
- [x] Focused on user value and business needs
|
||||
- [x] Written for non-technical stakeholders
|
||||
- [x] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||
- [x] Requirements are testable and unambiguous
|
||||
- [x] Success criteria are measurable
|
||||
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||
- [x] All acceptance scenarios are defined
|
||||
- [x] Edge cases are identified
|
||||
- [x] Scope is clearly bounded
|
||||
- [x] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria
|
||||
- [x] User scenarios cover primary flows
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [x] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- Validated against the drafted Spec 201 on 2026-04-18 after converting the source outline into the repository template and measurable requirement language.
|
||||
- Optional operator-surface classification tables are intentionally omitted because this feature governs workflow and review mechanics rather than changing a concrete runtime surface.
|
||||
- Filament and repository-signal terminology is intentional product vocabulary for this governance slice, not code-level implementation guidance.
|
||||
- No clarification markers remain. The spec is ready for `/speckit.plan`.
|
||||
@ -0,0 +1,450 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: UI Surface Enforcement & Review Guardrails
|
||||
version: 1.0.0
|
||||
description: |
|
||||
Logical contract for the repository-owned guardrail workflow introduced by Spec 201.
|
||||
It documents authoring, review, repository-signal, test-guardrail, and exception
|
||||
semantics. It is not a public HTTP API.
|
||||
servers:
|
||||
- url: https://tenantpilot.local/logical
|
||||
paths:
|
||||
/logical/ui-guardrails/spec-impact/validate:
|
||||
post:
|
||||
summary: Validate one spec-level UI guardrail impact block
|
||||
operationId: validateSpecGuardrailImpact
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SpecImpactValidationRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Spec guardrail impact evaluated
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SpecImpactValidationResult'
|
||||
/logical/ui-guardrails/reviews/classify:
|
||||
post:
|
||||
summary: Classify one UI or surface change through the reviewer checklist
|
||||
operationId: classifyReviewOutcome
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ReviewClassificationRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Review classification returned
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ReviewClassificationResult'
|
||||
/logical/ui-guardrails/repository-signals/assess:
|
||||
post:
|
||||
summary: Assess repository-level technical signals for a change
|
||||
operationId: assessRepositorySignals
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RepositorySignalAssessmentRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Repository-signal assessment returned
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RepositorySignalAssessmentResult'
|
||||
/logical/ui-guardrails/test-guardrails/resolve:
|
||||
post:
|
||||
summary: Resolve the required test profile for a surface contract class
|
||||
operationId: resolveTestGuardrailProfile
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TestGuardrailResolutionRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Test guardrail profile returned
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TestGuardrailResolutionResult'
|
||||
/logical/ui-guardrails/exceptions/assess:
|
||||
post:
|
||||
summary: Assess whether an exception record is complete and bounded
|
||||
operationId: assessExceptionWorkflow
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ExceptionAssessmentRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Exception assessment returned
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ExceptionAssessmentResult'
|
||||
/logical/ui-guardrails/closeout/prepare:
|
||||
post:
|
||||
summary: Prepare the active feature PR close-out entry for guarded work
|
||||
operationId: prepareCloseoutEntry
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CloseoutPreparationRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Close-out entry requirements returned
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CloseoutPreparationResult'
|
||||
/logical/ui-guardrails/guidance:
|
||||
get:
|
||||
summary: Read the current guardrail guidance pack
|
||||
operationId: readGuardrailGuidance
|
||||
responses:
|
||||
'200':
|
||||
description: Guardrail guidance returned
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GuardrailGuidancePack'
|
||||
components:
|
||||
schemas:
|
||||
SpecImpactValidationRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- specPath
|
||||
- uiSurfaceChange
|
||||
- nativeCustomClassification
|
||||
- stateLayers
|
||||
- sharedFamilyRelevance
|
||||
- exceptionNeeded
|
||||
properties:
|
||||
specPath:
|
||||
type: string
|
||||
uiSurfaceChange:
|
||||
type: boolean
|
||||
nativeCustomClassification:
|
||||
type: string
|
||||
stateLayers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- shell
|
||||
- page
|
||||
- detail
|
||||
sharedFamilyRelevance:
|
||||
type: string
|
||||
exceptionNeeded:
|
||||
type: boolean
|
||||
plannedGuardrailClasses:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
closeoutEntryTarget:
|
||||
type: string
|
||||
SpecImpactValidationResult:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- status
|
||||
- findings
|
||||
- closeoutEntryTarget
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- complete
|
||||
- needs-revision
|
||||
findings:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
reviewerHandOff:
|
||||
type: string
|
||||
closeoutEntryTarget:
|
||||
type: string
|
||||
ReviewClassificationRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- changeRef
|
||||
- triggeredMappings
|
||||
- checklistAnswers
|
||||
properties:
|
||||
changeRef:
|
||||
type: string
|
||||
triggeredMappings:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
checklistAnswers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
exceptionClaimed:
|
||||
type: boolean
|
||||
closeoutEntryTarget:
|
||||
type: string
|
||||
ReviewClassificationResult:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- outcomeClass
|
||||
- workflowOutcome
|
||||
- recordLocation
|
||||
properties:
|
||||
outcomeClass:
|
||||
type: string
|
||||
enum:
|
||||
- blocker
|
||||
- strong-warning
|
||||
- documentation-required-exception
|
||||
- acceptable-special-case
|
||||
workflowOutcome:
|
||||
type: string
|
||||
enum:
|
||||
- keep
|
||||
- split
|
||||
- document-in-feature
|
||||
- follow-up-spec
|
||||
- reject-or-split
|
||||
missingEvidence:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
recordLocation:
|
||||
type: string
|
||||
RepositorySignalAssessmentRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- changeRef
|
||||
- signalIds
|
||||
properties:
|
||||
changeRef:
|
||||
type: string
|
||||
signalIds:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
exceptionClaimed:
|
||||
type: boolean
|
||||
scopeNote:
|
||||
type: string
|
||||
RepositorySignalAssessmentResult:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- status
|
||||
- handlingModes
|
||||
- closeoutEntryRequired
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- complete
|
||||
- needs-review
|
||||
handlingModes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
requiresException:
|
||||
type: boolean
|
||||
closeoutEntryRequired:
|
||||
type: boolean
|
||||
notes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
TestGuardrailResolutionRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- surfaceClass
|
||||
properties:
|
||||
surfaceClass:
|
||||
type: string
|
||||
enum:
|
||||
- shared-detail-family
|
||||
- monitoring-state-page
|
||||
- global-context-shell
|
||||
- exception-coded-surface
|
||||
- standard-native-filament
|
||||
changedContractAreas:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
exceptionType:
|
||||
type: string
|
||||
TestGuardrailResolutionResult:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- requiredTestTypes
|
||||
- specialCoverageRequired
|
||||
- profileId
|
||||
properties:
|
||||
profileId:
|
||||
type: string
|
||||
requiredTestTypes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
manualSmokeExpectations:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
specialCoverageRequired:
|
||||
type: boolean
|
||||
ExceptionAssessmentRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- changeRef
|
||||
- exceptionType
|
||||
- brokenRules
|
||||
- boundaryPlan
|
||||
- closeoutEntryTarget
|
||||
properties:
|
||||
changeRef:
|
||||
type: string
|
||||
exceptionType:
|
||||
type: string
|
||||
brokenRules:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
productReason:
|
||||
type: string
|
||||
boundaryPlan:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
standardizationPlan:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
closeoutEntryTarget:
|
||||
type: string
|
||||
ExceptionAssessmentResult:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- status
|
||||
- spreadAllowed
|
||||
- closeoutEntryRequired
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- complete
|
||||
- needs-revision
|
||||
spreadAllowed:
|
||||
type: boolean
|
||||
closeoutEntryRequired:
|
||||
type: boolean
|
||||
closeoutNotes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
CloseoutPreparationRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- changeRef
|
||||
- outcomeClass
|
||||
- handlingModes
|
||||
- workflowOutcome
|
||||
properties:
|
||||
changeRef:
|
||||
type: string
|
||||
outcomeClass:
|
||||
type: string
|
||||
enum:
|
||||
- blocker
|
||||
- strong-warning
|
||||
- documentation-required-exception
|
||||
- acceptable-special-case
|
||||
handlingModes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
workflowOutcome:
|
||||
type: string
|
||||
enum:
|
||||
- keep
|
||||
- split
|
||||
- document-in-feature
|
||||
- follow-up-spec
|
||||
- reject-or-split
|
||||
proofDepth:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
duplicatePromptNotes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
CloseoutPreparationResult:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- entryName
|
||||
- requiredFields
|
||||
properties:
|
||||
entryName:
|
||||
type: string
|
||||
requiredFields:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
lowImpactAllowed:
|
||||
type: boolean
|
||||
notes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
GuardrailGuidancePack:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- entryPoints
|
||||
- outcomeClasses
|
||||
- workflowOutcomes
|
||||
- closeoutEntryName
|
||||
properties:
|
||||
entryPoints:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
outcomeClasses:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
workflowOutcomes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
closeoutEntryName:
|
||||
type: string
|
||||
notes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
@ -0,0 +1,451 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://tenantpilot.local/specs/201/guardrail-governance.schema.json",
|
||||
"title": "UI Surface Guardrail Governance Pack",
|
||||
"description": "Repository-owned contract for the review, repository-signal, test-guardrail, exception, and workflow-touchpoint artifacts introduced by Spec 201.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schemaVersion",
|
||||
"ruleMappings",
|
||||
"reviewChecklist",
|
||||
"repositorySignals",
|
||||
"testGuardrails",
|
||||
"exceptionWorkflow",
|
||||
"workflowTouchpoints",
|
||||
"validationScenarios",
|
||||
"closeoutEntry"
|
||||
],
|
||||
"properties": {
|
||||
"schemaVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"ruleMappings": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/ruleMapping"
|
||||
}
|
||||
},
|
||||
"reviewChecklist": {
|
||||
"$ref": "#/$defs/reviewChecklist"
|
||||
},
|
||||
"repositorySignals": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/repositorySignal"
|
||||
}
|
||||
},
|
||||
"testGuardrails": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/testGuardrail"
|
||||
}
|
||||
},
|
||||
"exceptionWorkflow": {
|
||||
"$ref": "#/$defs/exceptionWorkflow"
|
||||
},
|
||||
"workflowTouchpoints": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/workflowTouchpoint"
|
||||
}
|
||||
},
|
||||
"validationScenarios": {
|
||||
"type": "array",
|
||||
"minItems": 2,
|
||||
"items": {
|
||||
"$ref": "#/$defs/validationScenario"
|
||||
}
|
||||
},
|
||||
"closeoutEntry": {
|
||||
"$ref": "#/$defs/closeoutEntry"
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"ruleMapping": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"mappingId",
|
||||
"sourceRuleId",
|
||||
"problemClass",
|
||||
"operationalClasses",
|
||||
"handlingMode",
|
||||
"representativeCases",
|
||||
"targetWorkflowSurfaces"
|
||||
],
|
||||
"properties": {
|
||||
"mappingId": { "type": "string" },
|
||||
"sourceRuleId": { "type": "string" },
|
||||
"problemClass": { "type": "string" },
|
||||
"operationalClasses": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"review",
|
||||
"repository-signal",
|
||||
"test-guardrail",
|
||||
"exception",
|
||||
"workflow-touchpoint"
|
||||
]
|
||||
}
|
||||
},
|
||||
"handlingMode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"hard-stop-candidate",
|
||||
"review-mandatory",
|
||||
"exception-required",
|
||||
"report-only"
|
||||
]
|
||||
},
|
||||
"representativeCases": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"targetWorkflowSurfaces": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"deferredAutomation": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"reviewChecklist": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"checklistId",
|
||||
"questions",
|
||||
"outcomeClasses",
|
||||
"workflowOutcomes",
|
||||
"maxReviewMinutes"
|
||||
],
|
||||
"properties": {
|
||||
"checklistId": { "type": "string" },
|
||||
"questions": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": { "$ref": "#/$defs/reviewQuestion" }
|
||||
},
|
||||
"outcomeClasses": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"blocker",
|
||||
"strong-warning",
|
||||
"documentation-required-exception",
|
||||
"acceptable-special-case"
|
||||
]
|
||||
}
|
||||
},
|
||||
"workflowOutcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"keep",
|
||||
"split",
|
||||
"document-in-feature",
|
||||
"follow-up-spec",
|
||||
"reject-or-split"
|
||||
]
|
||||
}
|
||||
},
|
||||
"maxReviewMinutes": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"reviewQuestion": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"questionId",
|
||||
"category",
|
||||
"prompt",
|
||||
"appliesWhen",
|
||||
"linkedMappings",
|
||||
"evidenceExpected",
|
||||
"defaultOutcomeClass"
|
||||
],
|
||||
"properties": {
|
||||
"questionId": { "type": "string" },
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"surface-classification",
|
||||
"native-usage",
|
||||
"shared-family",
|
||||
"state-layer",
|
||||
"exception",
|
||||
"test-coverage"
|
||||
]
|
||||
},
|
||||
"prompt": { "type": "string" },
|
||||
"appliesWhen": { "type": "string" },
|
||||
"linkedMappings": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"evidenceExpected": { "type": "string" },
|
||||
"defaultOutcomeClass": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"blocker",
|
||||
"strong-warning",
|
||||
"documentation-required-exception",
|
||||
"acceptable-special-case"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"repositorySignal": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"signalId",
|
||||
"label",
|
||||
"patternType",
|
||||
"confidenceLevel",
|
||||
"handlingMode",
|
||||
"exceptionPath",
|
||||
"futurePromotion"
|
||||
],
|
||||
"properties": {
|
||||
"signalId": { "type": "string" },
|
||||
"label": { "type": "string" },
|
||||
"patternType": {
|
||||
"type": "string",
|
||||
"enum": ["grep", "search", "path-pattern", "manual"]
|
||||
},
|
||||
"searchScopePaths": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"triggerExamples": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"confidenceLevel": {
|
||||
"type": "string",
|
||||
"enum": ["high", "medium", "low"]
|
||||
},
|
||||
"handlingMode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"report-only",
|
||||
"review-mandatory",
|
||||
"exception-required",
|
||||
"hard-stop-candidate"
|
||||
]
|
||||
},
|
||||
"exceptionPath": { "type": "string" },
|
||||
"futurePromotion": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"testGuardrail": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"profileId",
|
||||
"surfaceClass",
|
||||
"triggers",
|
||||
"requiredTestTypes",
|
||||
"manualSmokeExpectations",
|
||||
"standardCoverageRule",
|
||||
"exceptionHandlingRule"
|
||||
],
|
||||
"properties": {
|
||||
"profileId": { "type": "string" },
|
||||
"surfaceClass": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"shared-detail-family",
|
||||
"monitoring-state-page",
|
||||
"global-context-shell",
|
||||
"exception-coded-surface",
|
||||
"standard-native-filament"
|
||||
]
|
||||
},
|
||||
"triggers": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"requiredTestTypes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"functional-core",
|
||||
"state-contract",
|
||||
"exception-fallback",
|
||||
"manual-smoke"
|
||||
]
|
||||
}
|
||||
},
|
||||
"manualSmokeExpectations": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"standardCoverageRule": { "type": "string" },
|
||||
"exceptionHandlingRule": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"exceptionWorkflow": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"workflowId",
|
||||
"supportedExceptionTypes",
|
||||
"requiredFields",
|
||||
"boundaryChecks",
|
||||
"standardizationFields",
|
||||
"spreadReviewRule",
|
||||
"closeoutRequirement",
|
||||
"closeoutNoteTarget"
|
||||
],
|
||||
"properties": {
|
||||
"workflowId": { "type": "string" },
|
||||
"supportedExceptionTypes": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"requiredFields": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"boundaryChecks": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"standardizationFields": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"spreadReviewRule": { "type": "string" },
|
||||
"closeoutRequirement": { "type": "string" },
|
||||
"closeoutNoteTarget": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"workflowTouchpoint": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"touchpointId",
|
||||
"artifactPath",
|
||||
"phase",
|
||||
"requiredPrompts",
|
||||
"linkedMappings",
|
||||
"outcomeTarget"
|
||||
],
|
||||
"properties": {
|
||||
"touchpointId": { "type": "string" },
|
||||
"artifactPath": { "type": "string" },
|
||||
"phase": {
|
||||
"type": "string",
|
||||
"enum": ["spec", "plan", "tasks", "review", "closeout", "follow-up"]
|
||||
},
|
||||
"requiredPrompts": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"linkedMappings": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"outcomeTarget": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"validationScenario": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"scenarioId",
|
||||
"scenarioType",
|
||||
"representativeArtifact",
|
||||
"expectedMappings",
|
||||
"expectedOutcomeClass",
|
||||
"expectedWorkflowOutcome",
|
||||
"status",
|
||||
"notes"
|
||||
],
|
||||
"properties": {
|
||||
"scenarioId": { "type": "string" },
|
||||
"scenarioType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low-impact",
|
||||
"fake-native",
|
||||
"shared-family",
|
||||
"state-layer",
|
||||
"legitimate-exception"
|
||||
]
|
||||
},
|
||||
"representativeArtifact": { "type": "string" },
|
||||
"expectedMappings": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"expectedOutcomeClass": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"blocker",
|
||||
"strong-warning",
|
||||
"documentation-required-exception",
|
||||
"acceptable-special-case"
|
||||
]
|
||||
},
|
||||
"expectedWorkflowOutcome": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"keep",
|
||||
"split",
|
||||
"document-in-feature",
|
||||
"follow-up-spec",
|
||||
"reject-or-split"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["planned", "validated", "needs-tuning"]
|
||||
},
|
||||
"notes": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"closeoutEntry": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"entryName",
|
||||
"requiredFields",
|
||||
"noteOwner",
|
||||
"lowImpactRule",
|
||||
"deferredAutomationField"
|
||||
],
|
||||
"properties": {
|
||||
"entryName": { "type": "string" },
|
||||
"requiredFields": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"noteOwner": { "type": "string" },
|
||||
"lowImpactRule": { "type": "string" },
|
||||
"deferredAutomationField": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
298
specs/201-enforcement-review-guardrails/data-model.md
Normal file
298
specs/201-enforcement-review-guardrails/data-model.md
Normal file
@ -0,0 +1,298 @@
|
||||
# Data Model: Enforcement & Review Guardrails
|
||||
|
||||
This feature adds repository-owned governance artifacts only. It does not add application database tables, runtime-owned entities, or executable service contracts. All objects below are implemented as markdown guidance, template prompts, checklists, and logical contracts.
|
||||
|
||||
Handling modes and review outcome classes are separate concepts in this model. Handling modes describe how a signal or rule is processed in the workflow, while review outcome classes describe the decision reached for a specific change.
|
||||
|
||||
## 1. GuardrailRuleMapping
|
||||
|
||||
**Purpose**: Maps one Spec 200 or constitution rule family to its operational behavior in review, repository signals, testing, exceptions, and workflow surfaces.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `mappingId` | string | Stable identifier for the mapping. |
|
||||
| `sourceRuleId` | string | Spec 200 or constitution rule family being operationalized. |
|
||||
| `problemClass` | string | Drift class addressed by the mapping. |
|
||||
| `operationalClasses` | array | One or more of `review`, `repository-signal`, `test-guardrail`, `exception`, `workflow-touchpoint`. |
|
||||
| `handlingMode` | string | One of `hard-stop-candidate`, `review-mandatory`, `exception-required`, or `report-only`. |
|
||||
| `representativeCases` | array | Real repo cases that prove the rule. |
|
||||
| `targetWorkflowSurfaces` | array | Templates, checklist surfaces, or docs that must express the mapping. |
|
||||
| `deferredAutomation` | array | Automation or CI work explicitly deferred from the first pass. |
|
||||
|
||||
**Validation Rules**
|
||||
|
||||
- Every mapping must point to an existing Spec 200 or constitution rule family.
|
||||
- Every mapping must declare at least one operational class and exactly one first-pass handling mode.
|
||||
- `hard-stop-candidate` mappings must either have a named exception path or explicitly state `none`.
|
||||
|
||||
## 2. ReviewChecklistQuestion
|
||||
|
||||
**Purpose**: Represents one mandatory reviewer question used to classify UI and surface work.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `questionId` | string | Stable identifier for the review question. |
|
||||
| `category` | string | `surface-classification`, `native-usage`, `shared-family`, `state-layer`, `exception`, or `test-coverage`. |
|
||||
| `prompt` | string | The exact decision-grade question. |
|
||||
| `appliesWhen` | string | Scope rule for when the question must be asked. |
|
||||
| `linkedMappings` | array | Related `GuardrailRuleMapping` IDs. |
|
||||
| `evidenceExpected` | string | What the author or reviewer must point to. |
|
||||
| `defaultOutcomeClass` | string | One of `blocker`, `strong-warning`, `documentation-required-exception`, or `acceptable-special-case`. |
|
||||
|
||||
**Validation Rules**
|
||||
|
||||
- Questions must be phrased as classification or review-stop decisions, not vague advice.
|
||||
- Every question must link to at least one guardrail mapping.
|
||||
- The question set must stay short enough for a representative review pass to complete in under 3 minutes.
|
||||
|
||||
## 3. RepositorySignalProfile
|
||||
|
||||
**Purpose**: Describes one technically observable red flag that can be reported or escalated during review.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `signalId` | string | Stable identifier for the signal. |
|
||||
| `label` | string | Human-readable signal name. |
|
||||
| `patternType` | string | `grep`, `search`, `path-pattern`, or `manual`. |
|
||||
| `searchScopePaths` | array | Paths where the signal applies. |
|
||||
| `triggerExamples` | array | Observable examples of the pattern. |
|
||||
| `confidenceLevel` | string | `high`, `medium`, or `low`. |
|
||||
| `handlingMode` | string | One of `report-only`, `review-mandatory`, `exception-required`, or `hard-stop-candidate`. |
|
||||
| `exceptionPath` | string | Named exception path or `none`. |
|
||||
| `futurePromotion` | string | Whether later hardening is allowed and under what condition. |
|
||||
|
||||
**Validation Rules**
|
||||
|
||||
- Every signal must have a documented handling mode and an exception or review path.
|
||||
- First-pass automation may not exceed `report-only` unless the signal has high confidence and low ambiguity.
|
||||
- Signals must stay anchored to real drift cases, not hypothetical future problems.
|
||||
|
||||
## 4. TestGuardrailProfile
|
||||
|
||||
**Purpose**: Defines the required test and smoke depth for a surface contract class.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `profileId` | string | Stable identifier for the surface test profile. |
|
||||
| `surfaceClass` | string | `shared-detail-family`, `monitoring-state-page`, `global-context-shell`, `exception-coded-surface`, or `standard-native-filament`. |
|
||||
| `triggers` | array | Conditions that activate the profile. |
|
||||
| `requiredTestTypes` | array | Required evidence such as `functional-core`, `state-contract`, `exception-fallback`, or `manual-smoke`. |
|
||||
| `manualSmokeExpectations` | array | Manual review or smoke expectations tied to the profile. |
|
||||
| `standardCoverageRule` | string | When ordinary feature coverage is enough. |
|
||||
| `exceptionHandlingRule` | string | How exceptions affect the profile. |
|
||||
|
||||
**Validation Rules**
|
||||
|
||||
- Every non-standard surface class must declare whether special coverage is mandatory.
|
||||
- `standard-native-filament` must explicitly state when no additional special guardrail tests are required.
|
||||
- Profiles must classify by interaction and contract risk, not just by framework or file type.
|
||||
|
||||
## 5. ExceptionWorkflowTemplate
|
||||
|
||||
**Purpose**: Captures the required fields and checks for a legitimate guardrail exception.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `templateId` | string | Stable identifier for the exception template. |
|
||||
| `exceptionType` | string | Canonical exception type from Spec 200. |
|
||||
| `brokenRuleIds` | array | Which rule families are being relaxed or not fully satisfied. |
|
||||
| `requiredFields` | array | Required justification fields the spec or PR must fill. |
|
||||
| `boundaryChecks` | array | What keeps the exception bounded. |
|
||||
| `standardizationFields` | array | What remains standardized despite the exception. |
|
||||
| `spreadReviewRule` | string | What must happen before the exception can extend to new hosts or surfaces. |
|
||||
| `closeoutNoteTarget` | string | Where the exception must be recorded at completion: the active feature PR close-out entry. |
|
||||
|
||||
**Validation Rules**
|
||||
|
||||
- Every exception must name the affected rule family and the product reason.
|
||||
- Boundary checks must be explicit enough to stop silent extension.
|
||||
- Exception approval is incomplete until the close-out note target is known.
|
||||
|
||||
## 6. WorkflowTouchpoint
|
||||
|
||||
**Purpose**: Defines one workflow artifact where guardrail behavior must appear.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `touchpointId` | string | Stable identifier for the workflow touchpoint. |
|
||||
| `artifactPath` | string | File path for the workflow surface. |
|
||||
| `phase` | string | `spec`, `plan`, `tasks`, `review`, `closeout`, or `follow-up`. |
|
||||
| `requiredPrompts` | array | Questions or fields the artifact must surface. |
|
||||
| `linkedMappings` | array | Related guardrail mappings. |
|
||||
| `outcomeTarget` | string | Where the touchpoint records its decision or handoff, including the active feature PR close-out entry when the phase is `closeout`. |
|
||||
|
||||
**Validation Rules**
|
||||
|
||||
- Every relevant phase from spec to closeout must have at least one touchpoint.
|
||||
- The active feature PR close-out entry is the single canonical close-out note target for guarded changes.
|
||||
- The same classification question should not be duplicated unnecessarily across multiple surfaces.
|
||||
- Touchpoints must point back to existing entry surfaces before any new file is introduced.
|
||||
|
||||
## 7. GuardrailAssessment
|
||||
|
||||
**Purpose**: Represents the result of classifying one UI or surface change through the workflow.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `assessmentId` | string | Stable identifier for one guardrail review. |
|
||||
| `triggeredMappings` | array | Activated rule mappings. |
|
||||
| `triggeredSignals` | array | Triggered repository signals, if any. |
|
||||
| `outcomeClass` | string | `blocker`, `strong-warning`, `documentation-required-exception`, or `acceptable-special-case`. |
|
||||
| `workflowOutcome` | string | `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`. |
|
||||
| `missingEvidence` | array | Missing inputs before the review can close. |
|
||||
| `recordLocation` | string | Spec, plan, checklist, or the active feature PR close-out entry where the result lives. |
|
||||
|
||||
**Validation Rules**
|
||||
|
||||
- `blocker` cannot resolve to `keep` while missing evidence remains.
|
||||
- `documentation-required-exception` requires a completed `ExceptionWorkflowTemplate` entry before the workflow can close as `keep` or `document-in-feature`.
|
||||
- `recordLocation` must resolve to one declared workflow touchpoint outcome or the active feature PR close-out entry.
|
||||
- `reject-or-split` is valid when hidden exception spread, unresolved state-layer conflict, or fake-native drift remains uncorrected.
|
||||
|
||||
## 8. ValidationScenario
|
||||
|
||||
**Purpose**: Represents one scenario used to prove the workflow is both lightweight and effective.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `scenarioId` | string | Stable scenario identifier. |
|
||||
| `scenarioType` | string | `low-impact`, `fake-native`, `shared-family`, `state-layer`, or `legitimate-exception`. |
|
||||
| `representativeArtifact` | string | Spec, template, or documentation artifact used in the validation walkthrough. |
|
||||
| `expectedMappings` | array | Rule mappings that should fire. |
|
||||
| `expectedOutcomeClass` | string | Expected guardrail outcome class. |
|
||||
| `expectedWorkflowOutcome` | string | Expected workflow outcome. |
|
||||
| `status` | string | `planned`, `validated`, or `needs-tuning`. |
|
||||
| `notes` | string | What the scenario proves. |
|
||||
|
||||
**Validation Rules**
|
||||
|
||||
- At least one `low-impact` and one high-risk scenario must be represented.
|
||||
- The scenario set must cover fake-native, shared-family, state-layer, and legitimate-exception reasoning.
|
||||
- Low-impact scenarios must prove that `N/A` paths remain fast and legitimate.
|
||||
|
||||
## Relationships
|
||||
|
||||
- One `GuardrailRuleMapping` may feed many `ReviewChecklistQuestion`, `RepositorySignalProfile`, and `WorkflowTouchpoint` entries.
|
||||
- A `RepositorySignalProfile` may trigger a `GuardrailAssessment`, but the final outcome still depends on the associated `ReviewChecklistQuestion` and any `ExceptionWorkflowTemplate`.
|
||||
- One `TestGuardrailProfile` may be referenced by many `GuardrailRuleMapping` entries when multiple rule families converge on the same surface class.
|
||||
- One `ExceptionWorkflowTemplate` may be required by many `GuardrailAssessment` records, but each assessment must keep its own bounded record location.
|
||||
- `ValidationScenario` objects prove that the combined model stays usable across both low-friction and high-risk cases.
|
||||
|
||||
## State Transitions
|
||||
|
||||
### GuardrailAssessment.workflowOutcome
|
||||
|
||||
- `keep` -> `document-in-feature`: allowed when a change is valid but the active feature still needs to record exception or signal handling explicitly.
|
||||
- `document-in-feature` -> `follow-up-spec`: allowed when review shows the issue is structural, recurring, or likely to need harder automation.
|
||||
- Any state -> `reject-or-split`: allowed when fake-native drift, hidden exception spread, unresolved host drift, or unresolved state-layer collapse remains.
|
||||
|
||||
### ValidationScenario.status
|
||||
|
||||
- `planned` -> `validated`: allowed when the workflow can classify the scenario with the expected outcome and without extra categories.
|
||||
- `planned` -> `needs-tuning`: allowed when wording is ambiguous, repetitive, or too heavy for the scenario.
|
||||
- `needs-tuning` -> `validated`: allowed after the relevant template, checklist, or guidance text is refined.
|
||||
|
||||
## Derived Outputs
|
||||
|
||||
The conceptual model must support these concrete outputs:
|
||||
|
||||
- a guardrail catalog that maps Spec 200 rule families to operational behavior
|
||||
- a reviewer question set with fixed outcome classes
|
||||
- a repository signal catalog with handling modes and exception escape paths
|
||||
- a surface-class test guardrail matrix
|
||||
- an exception workflow template with spread-control rules
|
||||
- workflow touchpoint changes in the spec, plan, task, checklist, and close-out surfaces
|
||||
- validation scenarios that prove both low-impact and high-risk usability
|
||||
|
||||
## Canonical Implementation Snapshot
|
||||
|
||||
### GuardrailRuleMapping Records
|
||||
|
||||
| `mappingId` | `sourceRuleId` | `problemClass` | `operationalClasses` | `handlingMode` | `representativeCases` | `targetWorkflowSurfaces` | `deferredAutomation` |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `GRM-001` | `UI-FIL-001`, `UI-HARD-001` | `fake-native-core-control` | `review`, `repository-signal`, `workflow-touchpoint` | `hard-stop-candidate` | Spec 196 fake-native cleanups | `spec-template`, `checklist-template`, `tasks-template`, `.specify/README.md` | `CI hard-stop deferred`, `grep remains report-first` |
|
||||
| `GRM-002` | `UI-REVIEW-001`, `UI-EX-001` | `shared-family-host-drift` | `review`, `test-guardrail`, `exception`, `workflow-touchpoint` | `review-mandatory` | Spec 197 shared detail contract | `spec-template`, `plan-template`, `checklist-template`, `tasks-template` | `family linting deferred` |
|
||||
| `GRM-003` | `DECIDE-001`, `OPSURF-001`, `UI-REVIEW-001` | `state-layer-ownership-collapse` | `review`, `repository-signal`, `test-guardrail`, `workflow-touchpoint` | `review-mandatory` | Specs 198 and 199 state-layer cases | `spec-template`, `plan-template`, `checklist-template`, `tasks-template` | `state-owner grep deferred` |
|
||||
| `GRM-004` | `UI-EX-001`, `UI-FIL-001` | `bounded-custom-surface-exception` | `review`, `exception`, `test-guardrail`, `workflow-touchpoint` | `exception-required` | Spec 200 legitimate special-case model | `spec-template`, `plan-template`, `checklist-template`, `.specify/README.md` | `exception bot deferred` |
|
||||
| `GRM-005` | `TEST-TRUTH-001`, `UI-FIL-001` | `standard-native-surface-relief` | `test-guardrail`, `workflow-touchpoint` | `report-only` | Standard native Filament work with no special contract | `plan-template`, `tasks-template`, `checklist-template` | `auto-relief detection deferred` |
|
||||
|
||||
### ReviewChecklistQuestion Records
|
||||
|
||||
| `questionId` | `category` | `prompt` | `appliesWhen` | `linkedMappings` | `evidenceExpected` | `defaultOutcomeClass` |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `CHK-SURF-001` | `surface-classification` | Does the change clearly state whether an operator-facing or guardrailed workflow surface is affected, or does it use one valid low-impact `N/A` path? | Always | `GRM-001`, `GRM-005` | Spec guardrail impact block | `acceptable-special-case` |
|
||||
| `CHK-NATIVE-001` | `native-usage` | Does the surface stay native/shared-primitives first, or is a custom surface claim being made explicitly? | Operator-facing surface changes | `GRM-001`, `GRM-004` | Surface classification, planned primitives, review notes | `blocker` |
|
||||
| `CHK-SHARED-001` | `shared-family` | If the surface belongs to a shared family, is the host/core boundary still one shared contract rather than a quiet fork? | Shared family or repeated micro-UI | `GRM-002` | Shared-family note, host variation description | `strong-warning` |
|
||||
| `CHK-STATE-001` | `state-layer` | Are shell, page, detail, and URL/query state owners named once and kept separate? | Shell, monitoring, stateful surfaces | `GRM-003` | State-layer summary in spec/plan | `strong-warning` |
|
||||
| `CHK-EXC-001` | `exception` | If default rules are intentionally relaxed, is there a bounded exception with spread control and a named close-out target? | Custom surface or rule relaxation | `GRM-004` | Exception record and close-out note target | `documentation-required-exception` |
|
||||
| `CHK-TEST-001` | `test-coverage` | Does the surface class use the right special profile, or explicit `standard-native-filament` relief? | Runtime or UI-surface changes | `GRM-002`, `GRM-003`, `GRM-004`, `GRM-005` | Test profile, manual smoke, validation commands | `strong-warning` |
|
||||
|
||||
### RepositorySignalProfile Records
|
||||
|
||||
| `signalId` | `label` | `patternType` | `searchScopePaths` | `triggerExamples` | `confidenceLevel` | `handlingMode` | `exceptionPath` | `futurePromotion` |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `SIG-001` | `fake-native-control` | `grep` | `app/Filament/**`, `resources/views/**` | Plain buttons or custom surface controls replacing native Filament semantics | `high` | `hard-stop-candidate` | `bounded-custom-surface-exception` | Hard-stop only after report-first validation stays low-noise |
|
||||
| `SIG-002` | `request-driven-page-state` | `grep` | `app/Filament/**`, `resources/views/**` | GET-form or request/query state driving page-body UI outside documented filter/shell patterns | `high` | `review-mandatory` | `none` | Promote only with later noise review |
|
||||
| `SIG-003` | `shared-family-host-fork` | `path-pattern` | `app/Filament/**`, `resources/views/**`, `docs/ui/**` | New host-specific partial or duplicated shared-family markup | `medium` | `review-mandatory` | `bounded-custom-surface-exception` | Future linting stays deferred |
|
||||
| `SIG-004` | `shell-context-leak` | `search` | `app/Filament/**`, `resources/views/**` | Workspace or tenant resolution logic leaks into presentation partials | `medium` | `review-mandatory` | `none` | Report-first only in this rollout |
|
||||
| `SIG-005` | `simple-overview-customization` | `manual` | `app/Filament/**`, `resources/views/**` | Hand-rolled overview/status UI where native widgets or shared primitives should remain primary | `medium` | `report-only` | `bounded-custom-surface-exception` | Never auto-block in first pass |
|
||||
|
||||
### TestGuardrailProfile Records
|
||||
|
||||
| `profileId` | `surfaceClass` | `triggers` | `requiredTestTypes` | `manualSmokeExpectations` | `standardCoverageRule` | `exceptionHandlingRule` |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `TGP-001` | `standard-native-filament` | Standard Resource/Page/RelationManager with no special shared, shell, or exception contract | none beyond ordinary feature coverage | Optional ordinary UI smoke only | Ordinary feature coverage is sufficient | If a later exception appears, switch to `exception-coded-surface` |
|
||||
| `TGP-002` | `shared-detail-family` | Reused detail micro-UI shared across multiple hosts | `functional-core` | Verify one representative host and the host/core boundary | Extra bespoke tests stay off unless the family contract changes | Host-specific divergence activates exception review |
|
||||
| `TGP-003` | `monitoring-state-page` | Monitoring or governance page owns its own state contract | `functional-core`, `state-contract` | Verify empty, loaded, and conflict states | Use the narrowest honest non-browser lane first | Local exception paths require explicit fallback coverage |
|
||||
| `TGP-004` | `global-context-shell` | Shell or context-recovery logic is changed | `state-contract`, `exception-fallback` | Verify valid, invalid, and recovery/fallback states | No extra browser family by default | Context exceptions require bounded fallback proof |
|
||||
| `TGP-005` | `exception-coded-surface` | Legitimate custom or special visualization relaxes a default rule | `functional-core`, `exception-fallback`, `manual-smoke` | Verify bounded exception behavior and preserved standardized parts | `standard-native-filament` relief does not apply | Close-out must justify the exception and the proof depth |
|
||||
|
||||
### ExceptionWorkflowTemplate Record
|
||||
|
||||
| `templateId` | `exceptionType` | `brokenRuleIds` | `requiredFields` | `boundaryChecks` | `standardizationFields` | `spreadReviewRule` | `closeoutNoteTarget` |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `EXW-001` | `bounded-custom-surface-exception` | `UI-FIL-001`, `UI-EX-001`, `UI-REVIEW-001` as applicable | Broken rule IDs, product reason, bounded surface/host, preserved standardized parts, required tests/manual smoke, deferred automation, active close-out note | No silent reuse on new hosts, no new helper spread without review, no alternate state owner introduced implicitly | Canonical noun, primary operator action, inspect model, standardized parts still preserved | Any extension to a new host, route, or surface requires renewed explicit review | Active feature PR close-out entry `Guardrail / Exception / Smoke Coverage` |
|
||||
|
||||
### WorkflowTouchpoint Records
|
||||
|
||||
| `touchpointId` | `artifactPath` | `phase` | `requiredPrompts` | `linkedMappings` | `outcomeTarget` |
|
||||
|---|---|---|---|---|---|
|
||||
| `WTP-001` | `.specify/templates/spec-template.md` | `spec` | UI / Surface Guardrail Impact block, low-impact `N/A` rule | `GRM-001`, `GRM-002`, `GRM-003`, `GRM-004` | `.specify/templates/plan-template.md` |
|
||||
| `WTP-002` | `.specify/templates/plan-template.md` | `plan` | Handling modes, repository-signal treatment, test profiles, close-out entry | `GRM-002`, `GRM-003`, `GRM-004`, `GRM-005` | `.specify/templates/tasks-template.md` |
|
||||
| `WTP-003` | `.specify/templates/tasks-template.md` | `tasks` | Carry-forward classification, proof tasks, exception documentation, close-out task | `GRM-001`, `GRM-002`, `GRM-003`, `GRM-004`, `GRM-005` | `.specify/templates/checklist-template.md` |
|
||||
| `WTP-004` | `.specify/templates/checklist-template.md` | `review` | Fixed reviewer questions, outcome class, workflow outcome | `GRM-001`, `GRM-002`, `GRM-003`, `GRM-004`, `GRM-005` | Active feature PR close-out entry |
|
||||
| `WTP-005` | `.specify/README.md` | `follow-up` | Author entry, review entry, low-impact rule, escalation rule, close-out rule | `GRM-001`, `GRM-004`, `GRM-005` | Active feature PR close-out entry |
|
||||
| `WTP-006` | `active feature PR close-out entry` | `closeout` | Scenario used, outcome class, handling mode, workflow outcome, proof depth, exception boundary, deferred automation | `GRM-002`, `GRM-003`, `GRM-004`, `GRM-005` | `Guardrail / Exception / Smoke Coverage` |
|
||||
|
||||
### ValidationScenario Records
|
||||
|
||||
| `scenarioId` | `scenarioType` | `representativeArtifact` | `expectedMappings` | `expectedOutcomeClass` | `expectedWorkflowOutcome` | `status` | `notes` |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `VAL-001` | `low-impact` | `.specify/templates/checklist-template.md` + `.specify/README.md` wording-only path | `GRM-005` | `acceptable-special-case` | `keep` | `validated` | Low-impact path stayed under one minute and used one `N/A` note only |
|
||||
| `VAL-002` | `fake-native` | `specs/196-hard-filament-nativity-cleanup/spec.md` | `GRM-001` | `blocker` | `reject-or-split` | `validated` | Fake-native drift remained the hardest signal |
|
||||
| `VAL-003` | `shared-family` | `specs/197-shared-detail-contract/spec.md` | `GRM-002` | `strong-warning` | `document-in-feature` | `validated` | Shared-family boundary still needs explicit reviewer judgment |
|
||||
| `VAL-004` | `state-layer` | `specs/198-monitoring-page-state/spec.md` + `specs/199-global-context-shell-contract/spec.md` | `GRM-003` | `strong-warning` | `document-in-feature` | `validated` | State-owner naming is mandatory before merge |
|
||||
| `VAL-005` | `legitimate-exception` | `specs/200-filament-surface-rules/spec.md` | `GRM-004` | `documentation-required-exception` | `document-in-feature` | `validated` | Legitimate special cases stay allowed only with bounded exception notes |
|
||||
|
||||
### Active Feature PR Close-out Entry
|
||||
|
||||
| Field | Requirement |
|
||||
|---|---|
|
||||
| `entryName` | `Guardrail / Exception / Smoke Coverage` |
|
||||
| `scenarioUsed` | Low-impact or representative guarded scenario used for validation |
|
||||
| `outcomeClass` | One of `blocker`, `strong-warning`, `documentation-required-exception`, or `acceptable-special-case` |
|
||||
| `handlingMode` | One of `hard-stop-candidate`, `review-mandatory`, `exception-required`, or `report-only` |
|
||||
| `workflowOutcome` | One of `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split` |
|
||||
| `proofDepth` | Required tests or manual smoke, or explicit `standard-native-filament` relief |
|
||||
| `exceptionBoundary` | `N/A` for low-impact or standard-native work, otherwise the bounded exception summary |
|
||||
| `duplicatePromptNotes` | Whether any classification question still appeared redundantly across workflow surfaces |
|
||||
| `deferredAutomation` | Any grep/lint/CI hardening explicitly deferred in the first pass |
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- database tables
|
||||
- Eloquent models or services
|
||||
- CI bots or grep scripts as implementation deliverables
|
||||
- runtime Livewire or Filament classes
|
||||
- public HTTP or CLI APIs
|
||||
- a new top-level handbook outside existing `.specify/` workflow surfaces
|
||||
176
specs/201-enforcement-review-guardrails/plan.md
Normal file
176
specs/201-enforcement-review-guardrails/plan.md
Normal file
@ -0,0 +1,176 @@
|
||||
# Implementation Plan: Enforcement & Review Guardrails
|
||||
|
||||
**Branch**: `201-enforcement-review-guardrails` | **Date**: 2026-04-18 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/201-enforcement-review-guardrails/spec.md`
|
||||
**Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/201-enforcement-review-guardrails/spec.md`
|
||||
|
||||
## Summary
|
||||
|
||||
Implement Spec 201 by keeping Spec 200 and `.specify/memory/constitution.md` as the normative rule source while operationalizing those rules through the existing SpecKit workflow surfaces: the spec, plan, task, and checklist templates; `.specify/README.md` as the author and reviewer entry point; and the feature-local guardrail artifacts that define review outcomes, repository-signal handling, required test depth, exception workflow, and close-out expectations. The implementation remains repository-governance only: no runtime UI, no CI hard gates in the first pass, and no second handbook or framework outside the current spec-driven flow.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: Markdown governance artifacts, JSON Schema plus logical OpenAPI planning contracts, and Bash-backed SpecKit scripts inside a PHP 8.4.15 / Laravel 12 / Filament v5 / Livewire v4 repository
|
||||
**Primary Dependencies**: `.specify/memory/constitution.md`, `.specify/templates/spec-template.md`, `.specify/templates/plan-template.md`, `.specify/templates/tasks-template.md`, `.specify/templates/checklist-template.md`, `.specify/README.md`, `docs/ui/operator-ux-surface-standards.md`, and Specs 196 through 200
|
||||
**Storage**: Repository-owned markdown and contract artifacts under `.specify/` and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/201-enforcement-review-guardrails/`; no product database persistence
|
||||
**Testing**: Document and template validation against low-impact and high-risk UI-governance scenarios, checklist completeness review, and no runtime Pest execution because the feature is docs and workflow only
|
||||
**Validation Lanes**: `N/A`
|
||||
**Target Platform**: TenantPilot monorepo workflow, SpecKit authoring flow, and Gitea-backed code review for Laravel operator-facing work
|
||||
**Project Type**: Monorepo with a Laravel platform app and Astro website, but this feature is scoped strictly to repository governance and authoring/review workflow artifacts
|
||||
**Performance Goals**: Keep low-impact UI-governance answers completable in under 1 minute, keep a representative guarded review under 3 minutes, and avoid duplicating the same guardrail question across multiple workflow surfaces
|
||||
**Constraints**: No runtime application code changes, no new CI bot or hard blocking grep/lint layer in the first pass, no parallel handbook or governance subsystem, explicit exception paths must remain available, and repository signals must support false-positive escape rather than becoming unconditional blockers
|
||||
**Scale/Scope**: One feature-local guardrail pack, four SpecKit templates, one `.specify/README.md` workflow entry point, optional light-touch operator-UX cross-reference only if wording alignment is necessary, and representative validation across low-impact plus the fake-native, shared-family, exception, and state-layer drift classes
|
||||
|
||||
### Filament v5 Implementation Notes
|
||||
|
||||
- **Livewire v4.0+ compliance**: Preserved. This feature changes repository workflow artifacts only and does not alter the Filament or Livewire runtime stack.
|
||||
- **Provider registration location**: Unchanged. Existing panel providers remain registered in `bootstrap/providers.php`.
|
||||
- **Global search rule**: No globally searchable resources are added or modified.
|
||||
- **Destructive actions**: No runtime destructive actions are introduced. The workflow artifacts added by this feature continue to require explicit exception and confirmation thinking for future operator-facing surfaces, but they do not change current action behavior.
|
||||
- **Asset strategy**: No panel or shared assets are added. Existing `filament:assets` deployment behavior remains unchanged.
|
||||
- **Testing plan**: Validate the guardrail model, template prompts, checklist wording, repository-signal handling, exception documentation, and workflow fit against one low-impact docs-only path and representative high-risk UI-surface cases grounded in Specs 196 through 200; no runtime UI, action, or Livewire tests are added by this feature.
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
- Inventory-first: PASS. No inventory, snapshot, or backup product truth changes.
|
||||
- Read/write separation: PASS. This is repository-only governance work with no end-user mutations.
|
||||
- Graph contract path: PASS. No Microsoft Graph calls or contract-registry changes.
|
||||
- Deterministic capabilities: PASS. No capability resolver, policy, or authorization registry changes.
|
||||
- RBAC-UX, workspace isolation, tenant isolation: PASS. No runtime routes, surfaces, or policy behavior changes.
|
||||
- Run observability and Ops-UX: PASS. No `OperationRun` lifecycle or monitoring behavior changes.
|
||||
- Data minimization: PASS. The new artifacts are repository-owned prompts, schemas, and guidance only.
|
||||
- Test governance (TEST-GOV-001): PASS WITH WORK. The feature is docs-only, so validation lanes remain `N/A`, but the resulting workflow must still define how special UI surface classes trigger extra tests or smoke expectations in future runtime work.
|
||||
- Proportionality and bloat control: PASS WITH LIMITS. The implementation touches several workflow entry points, but it stays inside the existing authoring and review chain rather than creating a new subsystem.
|
||||
- UI semantics and few-layers rules: PASS. The plan reuses Spec 200 vocabulary and does not add a new semantic framework or parallel rulebook.
|
||||
- Filament-native and surface-taxonomy rules: PASS WITH WORK. The workflow artifacts must reuse the exact Spec 200 and constitution vocabulary rather than invent alternative guardrail terms.
|
||||
- Decision-first and operator-surface rules: PASS. No new runtime surface is introduced; the plan only makes existing review expectations operational.
|
||||
|
||||
**Phase 0 Gate Result**: PASS
|
||||
|
||||
- The feature stays bounded to repository governance, templates, checklist prompts, and review guidance.
|
||||
- No new product persistence, runtime routes, Graph seams, or authorization behavior is introduced.
|
||||
- The operationalization path keeps Spec 200 as the rule source instead of reopening the constitution with a new competing layer.
|
||||
|
||||
## Test Governance Check
|
||||
|
||||
- **Affected validation lanes**: `N/A`
|
||||
- **Narrowest proving command(s)**: `N/A`. Validation is document- and workflow-based rather than runtime-lane based.
|
||||
- **Fixture / helper cost risks**: None directly. The feature exists to force future surface work to expose needed tests and smoke depth, not to introduce new runtime setup.
|
||||
- **Heavy-family additions or promotions**: None. The intended change is earlier disclosure of required test depth for special surface classes in future specs and reviews.
|
||||
- **Budget / baseline / trend follow-up**: None directly. The feature must keep docs-only changes answerable with `N/A` while still making future runtime test obligations explicit.
|
||||
- **Why no dedicated follow-up spec is needed**: Spec 201 is itself the workflow-guardrail feature. Routine upkeep should stay inside ordinary feature specs after rollout unless later automation or hard gating justifies a separate follow-up spec.
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/201-enforcement-review-guardrails/
|
||||
├── plan.md
|
||||
├── research.md
|
||||
├── data-model.md
|
||||
├── quickstart.md
|
||||
├── contracts/
|
||||
│ ├── guardrail-governance.schema.json
|
||||
│ └── guardrail-governance.logical.openapi.yaml
|
||||
└── tasks.md
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
|
||||
```text
|
||||
.specify/
|
||||
├── README.md
|
||||
├── memory/
|
||||
│ └── constitution.md
|
||||
└── templates/
|
||||
├── checklist-template.md
|
||||
├── plan-template.md
|
||||
├── spec-template.md
|
||||
└── tasks-template.md
|
||||
|
||||
docs/
|
||||
└── ui/
|
||||
└── operator-ux-surface-standards.md
|
||||
|
||||
specs/
|
||||
├── 196-hard-filament-nativity-cleanup/
|
||||
├── 197-shared-detail-contract/
|
||||
├── 198-monitoring-page-state/
|
||||
├── 199-global-context-shell-contract/
|
||||
├── 200-filament-surface-rules/
|
||||
└── 201-enforcement-review-guardrails/
|
||||
```
|
||||
|
||||
**Structure Decision**: Keep changes concentrated in the existing workflow surfaces under `.specify/templates/` and `.specify/README.md`, with `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/201-enforcement-review-guardrails/` holding the feature-local catalog and contract artifacts. `.specify/memory/constitution.md`, `docs/ui/operator-ux-surface-standards.md`, and Specs 196 through 200 are primary references; they should only be edited if a minimal wording alignment is required to avoid ambiguity, not as a second implementation surface.
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||
|-----------|------------|-------------------------------------|
|
||||
| None | Not applicable | Not applicable |
|
||||
|
||||
## Proportionality Review
|
||||
|
||||
- **Current operator problem**: The repo now knows what UI and surface drift looks like, but authors and reviewers still lack one repeatable workflow that turns those rules into concrete review questions, reportable repository signals, required test depth, and visible exception handling before merge.
|
||||
- **Existing structure is insufficient because**: Spec 200 and the constitution define the rule language, but the current spec, plan, task, and checklist surfaces do not yet force authors or reviewers to classify native versus custom work, state layers, shared-family relevance, repository-signal handling, or exception boundaries consistently.
|
||||
- **Narrowest correct implementation**: Tighten the existing SpecKit templates and `.specify/README.md`, publish a feature-local guardrail catalog with structured contracts, and validate the workflow against representative real repo cases rather than adding bots, CI policy, or a new handbook.
|
||||
- **Ownership cost created**: The repo must maintain one shared guardrail vocabulary across templates, review checklists, and workflow guidance, plus a small set of structured planning contracts that describe the model.
|
||||
- **Alternative intentionally rejected**: A new CI-based enforcement system, a separate UI review handbook, or hard blocking grep/lint automation in the first pass, because each would widen process surface and false-positive risk before the workflow itself is proven.
|
||||
- **Release truth**: Current-release repository truth needed now so Spec 200 does not immediately decay back into “agreed in principle, optional in practice.”
|
||||
|
||||
## Phase 0 — Research (complete)
|
||||
|
||||
- Output: [research.md](./research.md)
|
||||
- Resolved key decisions:
|
||||
- Keep Spec 200 plus `.specify/memory/constitution.md` as the normative rule source and operationalize through templates and review guidance instead of reopening the rulebook.
|
||||
- Reuse the existing workflow entry points under `.specify/templates/` and `.specify/README.md` rather than introducing a new handbook or contributor process.
|
||||
- Treat repository signals as report-first and exception-aware in the first pass, with future promotion paths documented but not enforced automatically.
|
||||
- Separate guardrail classes from handling modes so the same rule family can be review-only, technically signalable, test-triggering, or exception-bound without unstable vocabulary.
|
||||
- Model required test depth by surface contract class rather than by file type or framework.
|
||||
- Use logical schema/OpenAPI artifacts as structured planning contracts for the guardrail pack instead of fabricating runtime APIs.
|
||||
- Validate the workflow with one low-impact docs-only scenario plus representative fake-native, shared-family, exception, and state-layer scenarios grounded in Specs 196 through 200.
|
||||
|
||||
## Phase 1 — Design & Contracts (baseline complete)
|
||||
|
||||
- Output: [data-model.md](./data-model.md) formalizes the repository-owned governance entities: rule mappings, review checklist questions, repository signals, test-guardrail profiles, exception workflows, workflow touchpoints, and validation scenarios.
|
||||
- Output: [contracts/guardrail-governance.schema.json](./contracts/guardrail-governance.schema.json) defines the schema-first planning contract for the complete guardrail pack; it is not a runtime or transport API.
|
||||
- Output: [contracts/guardrail-governance.logical.openapi.yaml](./contracts/guardrail-governance.logical.openapi.yaml) captures the logical workflow semantics for classifying specs, evaluating review checklists, resolving repository signals, determining test requirements, and assessing exceptions; it is not a public HTTP contract.
|
||||
- Output: [quickstart.md](./quickstart.md) provides the implementation order, representative validation walkthroughs, and rollout checklist.
|
||||
- These artifacts are design-complete enough for planning, but Phase 2 implementation work still stabilizes wording, timing checks, close-out targeting, and cross-artifact alignment before template rollout.
|
||||
|
||||
### Post-design Constitution Re-check
|
||||
|
||||
- PASS: No runtime routes, panels, Graph seams, assets, or authorization planes are introduced.
|
||||
- PASS: The design keeps Spec 200 and the constitution as the single rule source and uses templates only for operationalization.
|
||||
- PASS: The workflow surfaces stay inside existing `.specify/` entry points rather than creating a second governance subsystem.
|
||||
- PASS WITH WORK: Template prompts and checklist items must avoid asking the same classification question redundantly across spec, plan, task, and review surfaces.
|
||||
- PASS WITH WORK: Repository signals must remain review-triggering and exception-aware in the first pass so legitimate custom surfaces do not become permanent false positives.
|
||||
|
||||
## Phase 2 — Implementation Planning
|
||||
|
||||
`tasks.md` should cover:
|
||||
|
||||
- Auditing the current guardrail-relevant workflow surfaces and mapping the relevant Spec 200 and constitution rule families to review guardrails, repository signals, test triggers, exception handling, and workflow touchpoints.
|
||||
- Publishing the feature-local guardrail catalog and logical contracts in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/201-enforcement-review-guardrails/` as the implementation's structured design reference.
|
||||
- Updating `.specify/templates/spec-template.md` so UI and surface-relevant specs explicitly capture native versus custom classification, state-layer ownership, shared-family relevance, exception need, and low-impact `N/A` handling where no operator-facing surface changes exist.
|
||||
- Updating `.specify/templates/plan-template.md` so planning surfaces explicitly capture guardrail handling modes, planned repository-signal treatment, required test depth for special surface classes, and the active feature PR close-out entry as the close-out note target for exceptions and smoke coverage.
|
||||
- Updating `.specify/templates/tasks-template.md` so task generation carries forward native/custom, state-layer, shared-family, and exception classification into implementation tasks and requires review classification work, definition-of-done checks, exception documentation, required tests or smoke checks, and the active feature PR close-out entry whenever a special surface class is involved.
|
||||
- Updating `.specify/templates/checklist-template.md` as the canonical review-checklist surface so reviewers can apply the fixed question set and end with both a guardrail outcome class and an explicit workflow outcome such as `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`.
|
||||
- Updating `.specify/README.md` with a concise author and reviewer entry guide that explains low-impact `N/A` handling, review-stop expectations, and when exception spread or structural drift requires escalation.
|
||||
- Touching `docs/ui/operator-ux-surface-standards.md` only if one minimal cross-reference is needed to point template users back to the guardrail workflow without duplicating the rules.
|
||||
- Validating the updated workflow against a low-impact docs-only change plus representative fake-native, shared-family, exception, and shell/page/detail state scenarios derived from Specs 196 through 200.
|
||||
- Recording explicit first-pass deferrals for grep/lint/CI hardening so later automation can be added deliberately rather than assumed by template wording.
|
||||
|
||||
### Contract Implementation Note
|
||||
|
||||
- The JSON schema is repository-tooling oriented and describes the guardrail pack the workflow must express during planning, even if the first implementation lives primarily in markdown templates and checklists.
|
||||
- The OpenAPI file is logical rather than transport-prescriptive. It documents workflow semantics for authoring, review, signal assessment, and exception handling, not a runtime service.
|
||||
- The design intentionally avoids new runtime services, CLI tooling, or CI bots. The contract artifacts are structured planning aids inside this feature set, not a new maintained subsystem.
|
||||
|
||||
### Deployment Sequencing Note
|
||||
|
||||
- No database migration is planned.
|
||||
- No asset publish step changes.
|
||||
- Recommended rollout order: finalize the feature-local guardrail pack first, then update the SpecKit templates, then update `.specify/README.md`, then add any minimal secondary cross-reference if needed, then run the representative validation walkthroughs, and finally record which automation candidates remain intentionally deferred.
|
||||
147
specs/201-enforcement-review-guardrails/quickstart.md
Normal file
147
specs/201-enforcement-review-guardrails/quickstart.md
Normal file
@ -0,0 +1,147 @@
|
||||
# Quickstart: Implementing Spec 201
|
||||
|
||||
This feature is repository-governance only. It does not change application runtime behavior, routes, assets, or validation lanes. The goal is to make future UI and surface work classify drift, exceptions, and required test depth earlier in the existing spec-driven workflow.
|
||||
|
||||
## 1. Confirm the rule and workflow surfaces
|
||||
|
||||
Start with the existing sources that already carry UI and surface governance truth:
|
||||
|
||||
- `.specify/memory/constitution.md`
|
||||
- `.specify/templates/spec-template.md`
|
||||
- `.specify/templates/plan-template.md`
|
||||
- `.specify/templates/tasks-template.md`
|
||||
- `.specify/templates/checklist-template.md`
|
||||
- `.specify/README.md`
|
||||
- `docs/ui/operator-ux-surface-standards.md`
|
||||
- `specs/196-hard-filament-nativity-cleanup/`
|
||||
- `specs/197-shared-detail-contract/`
|
||||
- `specs/198-monitoring-page-state/`
|
||||
- `specs/199-global-context-shell-contract/`
|
||||
- `specs/200-filament-surface-rules/`
|
||||
|
||||
Do not create a parallel handbook or a second normative rule source. Spec 200 and the constitution remain the rule source; Spec 201 operationalizes them.
|
||||
|
||||
## 2. Build the guardrail catalog first
|
||||
|
||||
Before changing templates, map the relevant rule families from Spec 200 into the operational model:
|
||||
|
||||
- which cases are structured review questions
|
||||
- which cases are technically signalable
|
||||
- which cases require additional tests or smoke expectations
|
||||
- which cases are legitimate only through a named exception path
|
||||
- which cases are only future hard-stop candidates rather than immediate blockers
|
||||
|
||||
Keep the class of the problem separate from the handling mode. The guardrail pack should be able to say both “this is a repository signal” and “its first-pass handling mode is review-mandatory.” Keep handling modes separate from review outcome classes as well: handling modes describe how the workflow treats a pattern, while review outcomes stay limited to `blocker`, `strong-warning`, `documentation-required-exception`, and `acceptable-special-case`.
|
||||
|
||||
## 3. Update the workflow surfaces in order
|
||||
|
||||
Apply the same vocabulary and outcome model consistently, in this order:
|
||||
|
||||
1. `spec-template.md`
|
||||
Add the authoring-time prompts that require native/custom classification, shared-family relevance, state-layer ownership, exception need, and low-impact `N/A` handling for features that do not touch operator-facing surfaces.
|
||||
2. `plan-template.md`
|
||||
Add planning-time prompts that require guardrail handling modes, repository-signal treatment, required test depth for special surface classes, and the active feature PR close-out entry for exceptions and smoke coverage.
|
||||
3. `tasks-template.md`
|
||||
Add task-level obligations that carry forward native/custom, state-layer, shared-family, and exception classification into implementation work and require review classification, definition-of-done checks, required tests or smoke checks, exception documentation, and the active feature PR close-out entry when a special surface class is involved.
|
||||
4. `checklist-template.md`
|
||||
Make this the canonical reviewer checklist surface with the fixed question set and the explicit workflow outcomes: `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`.
|
||||
5. `.specify/README.md`
|
||||
Keep one concise author/reviewer entry guide that explains low-impact `N/A` handling, review-stop expectations, the active feature PR close-out entry, and when structural drift needs escalation.
|
||||
|
||||
Avoid asking the same classification question in three different ways across the workflow.
|
||||
|
||||
## 4. Keep the standards doc secondary
|
||||
|
||||
Use `docs/ui/operator-ux-surface-standards.md` only as a supporting alignment document.
|
||||
|
||||
- Add one light cross-reference only if template users need help finding the guardrail workflow.
|
||||
- Do not duplicate the full guardrail catalog or review checklist there.
|
||||
|
||||
## 5. Run the required validation walkthroughs
|
||||
|
||||
The implementation is only complete if the workflow can classify both a low-friction case and the target drift classes it is meant to stop.
|
||||
|
||||
### Low-impact workflow path
|
||||
|
||||
Use a genuinely low-impact docs-only change, such as a wording-only change in `.specify/templates/checklist-template.md` or `.specify/README.md`, to prove that:
|
||||
|
||||
- the spec and plan prompts can be answered with `N/A` where appropriate
|
||||
- the checklist can still close with `keep`
|
||||
- no fake runtime or CI obligations are introduced
|
||||
- the low-impact path remains completable in under 1 minute
|
||||
|
||||
Expected result: the low-impact path remains fast and does not force invented UI-surface prose.
|
||||
|
||||
### Representative guarded cases
|
||||
|
||||
Use the following repo cases to prove the workflow has real discrimination power:
|
||||
|
||||
| Case class | Source artifact | Expected outcome |
|
||||
|---|---|---|
|
||||
| Fake-native hard signal | `specs/196-hard-filament-nativity-cleanup/` | classified as a hard technical signal and a blocker unless a bounded exception is explicit |
|
||||
| Shared-family host drift | `specs/197-shared-detail-contract/` | classified as review-mandatory with host/core contract checks |
|
||||
| Shell/page/detail confusion | `specs/198-monitoring-page-state/` and `specs/199-global-context-shell-contract/` | classified as review-mandatory with explicit owner-layer naming |
|
||||
| Legitimate special visualization | `specs/200-filament-surface-rules/` exception model | classified as documentation-required exception rather than default violation |
|
||||
|
||||
Expected result: no new category is needed during review. The workflow can classify each case using only the defined guardrail language.
|
||||
The representative guarded review should remain completable in under 3 minutes, and the walkthrough should record any classification question that appears redundantly across spec, plan, task, review, and close-out surfaces.
|
||||
|
||||
## 6. Recorded validation results
|
||||
|
||||
### Reviewer workflow validation
|
||||
|
||||
| Scenario | Source artifact | Outcome class | Handling mode | Workflow outcome | Result |
|
||||
|---|---|---|---|---|---|
|
||||
| Low-impact docs-only path | `.specify/templates/checklist-template.md` + `.specify/README.md` | `acceptable-special-case` | `report-only` | `keep` | Completed in `00:48` with one explicit `N/A` note and no invented surface prose |
|
||||
| Fake-native hard signal | `specs/196-hard-filament-nativity-cleanup/spec.md` | `blocker` | `hard-stop-candidate` | `reject-or-split` | Checklist still lands on the strongest outcome without creating a new category |
|
||||
| Shared-family host drift | `specs/197-shared-detail-contract/spec.md` | `strong-warning` | `review-mandatory` | `document-in-feature` | Shared-family boundary stays reviewable without becoming fake automation |
|
||||
| State-layer confusion | `specs/198-monitoring-page-state/spec.md` + `specs/199-global-context-shell-contract/spec.md` | `strong-warning` | `review-mandatory` | `document-in-feature` | State ownership is named once instead of spread across surfaces |
|
||||
| Legitimate special visualization | `specs/200-filament-surface-rules/spec.md` | `documentation-required-exception` | `exception-required` | `document-in-feature` | Bounded exception remains allowed without weakening the default rule |
|
||||
|
||||
- Representative guarded review elapsed time: `02:34`
|
||||
- Duplicate-prompt note: native/custom, shared-family, and state-layer classification now originate in the spec guardrail block and are re-used downstream instead of being rephrased.
|
||||
|
||||
### Authoring workflow validation
|
||||
|
||||
| Scenario | Source artifact | Result | Time | Notes |
|
||||
|---|---|---|---|---|
|
||||
| Low-impact docs-only scenario | `.specify/templates/checklist-template.md` + `.specify/README.md` | `keep` | `00:48` | One `N/A` path stayed sufficient through spec, plan, and review |
|
||||
| Surface-changing scenario | `specs/200-filament-surface-rules/spec.md` | `document-in-feature` | `01:51` | Native/custom classification, handling modes, and proof depth stayed explicit without a second workflow |
|
||||
|
||||
## 7. Record the validation note
|
||||
|
||||
Capture the implementation proof in the active feature PR close-out entry with at least:
|
||||
|
||||
- the low-impact scenario used
|
||||
- the representative guarded cases used
|
||||
- which review outcome class and handling mode each case reached
|
||||
- which workflow outcome was chosen
|
||||
- the elapsed low-impact and guarded-review completion times
|
||||
- whether any template wording felt redundant or missing
|
||||
- whether any classification question was duplicated across workflow surfaces
|
||||
- that the active feature PR close-out entry owns the final completion note
|
||||
- whether any automation candidate was explicitly deferred
|
||||
|
||||
The active feature PR close-out entry name for this rollout is `Guardrail / Exception / Smoke Coverage`.
|
||||
|
||||
## 8. First-pass automation deferrals
|
||||
|
||||
- Keep `SIG-001` through `SIG-005` report-first. No grep, lint, or CI rule blocks merge automatically in this rollout.
|
||||
- Defer any bot or generated reminder that enforces the close-out entry name.
|
||||
- Defer repo-wide shared-family host-fork detection until the exception escape path has proven low noise.
|
||||
- Defer automatic promotion from `review-mandatory` to blocking until a later spec reviews misses, noise, and ownership cost.
|
||||
|
||||
## 9. Completion checklist
|
||||
|
||||
- Guardrail catalog and contracts exist in the active feature artifact set
|
||||
- Template prompts use the same vocabulary as Spec 200 and the constitution
|
||||
- `.specify/README.md` gives a concise author/reviewer entry path
|
||||
- Low-impact `N/A` handling remains legitimate and fast
|
||||
- Low-impact validation remains completable in under 1 minute
|
||||
- Representative fake-native, shared-family, state-layer, and exception cases classify cleanly
|
||||
- Representative guarded review remains completable in under 3 minutes
|
||||
- No classification question is duplicated unnecessarily across spec, plan, tasks, review, and close-out surfaces
|
||||
- The active feature PR close-out entry is named `Guardrail / Exception / Smoke Coverage`
|
||||
- First-pass repository signals remain report-first and exception-aware
|
||||
- First-pass automation deferrals are recorded explicitly
|
||||
- No second handbook, runtime tool, or CI hard-gate subsystem was introduced
|
||||
114
specs/201-enforcement-review-guardrails/research.md
Normal file
114
specs/201-enforcement-review-guardrails/research.md
Normal file
@ -0,0 +1,114 @@
|
||||
# Research: Enforcement & Review Guardrails
|
||||
|
||||
## Decision 1: Keep Spec 200 and the constitution as the only normative rule source
|
||||
|
||||
- **Decision**: Treat Spec 200 and `.specify/memory/constitution.md` as the source of truth for UI and surface rules, and use Spec 201 only to operationalize those rules through workflow artifacts.
|
||||
- **Rationale**: Spec 201 exists to make the existing rules early-visible and reviewable, not to restate or replace them. Reopening the constitution as a second rule-writing exercise would recreate the same split-source problem this feature is meant to solve.
|
||||
- **Alternatives considered**:
|
||||
- Add a new top-level guardrail rulebook: rejected because it would force reviewers to reconcile two overlapping authorities.
|
||||
- Keep guardrails only in feature-local docs: rejected because the workflow would then lack shared authoring and review touchpoints.
|
||||
|
||||
## Decision 2: Reuse the existing SpecKit workflow surfaces instead of creating a new process
|
||||
|
||||
- **Decision**: Put the operational changes into `.specify/templates/spec-template.md`, `.specify/templates/plan-template.md`, `.specify/templates/tasks-template.md`, `.specify/templates/checklist-template.md`, and `.specify/README.md`.
|
||||
- **Rationale**: These are the surfaces contributors already use when drafting, planning, tasking, and reviewing feature work. Tightening them creates leverage where decisions are already made, without adding another process entry point.
|
||||
- **Alternatives considered**:
|
||||
- Create a new dedicated handbook under `docs/`: rejected because it would be easier to ignore and harder to keep in sync with the live workflow.
|
||||
- Put all guidance only into `.specify/memory/constitution.md`: rejected because authors and reviewers need operational prompts where they already work, not just high-level rules.
|
||||
|
||||
## Decision 3: Keep first-pass repository signals report-first and exception-aware
|
||||
|
||||
- **Decision**: Model technical signals as report-only, review-mandatory, exception-required, or future hard-stop candidates, but do not introduce automatic hard CI blocking in the first pass.
|
||||
- **Rationale**: Several anti-patterns are grep-able, but legitimate custom surfaces and bounded exceptions still require human judgment. The first operational layer should make likely drift visible early without making the repo noisy or brittle.
|
||||
- **Alternatives considered**:
|
||||
- Immediate CI failures for all grep-detectable patterns: rejected because false positives would be too high before the exception path and vocabulary are stabilized.
|
||||
- Purely narrative guidance with no signal catalog: rejected because authors and reviewers need concrete early-warning patterns, not only abstract rules.
|
||||
|
||||
## Decision 4: Separate guardrail classes from handling modes
|
||||
|
||||
- **Decision**: Distinguish the class of a guardrail problem from how it is handled. The stable classes are review guardrails, repository signals, test guardrails, exception workflows, and workflow touchpoints; the handling modes are hard-stop candidate, review-mandatory, exception-required, and report-only.
|
||||
- **Rationale**: The same product rule can show up in multiple operational ways. For example, a fake-native case is both a review question and a technical signal, while a special visualization may be review-mandatory and exception-required but never grep-detectable. Separating class from handling mode keeps the vocabulary stable.
|
||||
- **Alternatives considered**:
|
||||
- Collapse class and handling into one flat list: rejected because it would blur the difference between “what kind of problem is this?” and “how should the workflow react?”
|
||||
- Use only blocker versus non-blocker: rejected because the spec explicitly needs finer distinctions for exceptions and future hardening.
|
||||
|
||||
## Decision 5: Model required test depth by surface contract class, not by technology alone
|
||||
|
||||
- **Decision**: Drive test-guardrail requirements from the surface contract class: shared detail family, monitoring/governance page with its own state contract, global context shell surface, exception-coded custom surface, or standard native Filament surface.
|
||||
- **Rationale**: The testing requirement is about interaction and state risk, not just whether the surface uses Filament, Blade, or Livewire. This keeps test depth proportional and avoids forcing bespoke tests onto ordinary standard surfaces.
|
||||
- **Alternatives considered**:
|
||||
- Require extra tests for every Filament change: rejected because it would create friction without better risk discrimination.
|
||||
- Leave test depth fully discretionary: rejected because the spec explicitly requires required-test signals for certain surface classes.
|
||||
|
||||
## Decision 6: Keep the exception workflow explicit and spread-sensitive
|
||||
|
||||
- **Decision**: Require each exception to name the broken or relaxed rule, the product reason, the boundary, the standardized parts that remain, and the spread-control rule that prevents silent extension to new hosts or surfaces.
|
||||
- **Rationale**: Hidden exceptions are one of the target problem classes. The workflow must make “one-off but allowed” visibly different from “new precedent by drift.”
|
||||
- **Alternatives considered**:
|
||||
- Let each spec describe its exception informally: rejected because that would recreate the hidden-exception pattern.
|
||||
- Ban most exceptions outright: rejected because legitimate special visualizations and bounded state exceptions are already part of the intended product model.
|
||||
|
||||
## Decision 7: Use logical contracts for the guardrail pack, not runtime APIs
|
||||
|
||||
- **Decision**: Create a JSON Schema and a logical OpenAPI file under `contracts/` that describe the planning-time guardrail pack and its workflow semantics.
|
||||
- **Rationale**: Adjacent governance specs already use structured contract artifacts to make non-runtime workflow truth explicit. Reusing that pattern keeps planning artifacts consistent and gives future task generation a concrete model.
|
||||
- **Alternatives considered**:
|
||||
- Markdown-only notes in `contracts/`: rejected because they are less structured for downstream use.
|
||||
- Real HTTP or CLI API contracts: rejected because this feature does not introduce a runtime service.
|
||||
|
||||
## Decision 8: Keep operator-UX standards as a secondary alignment document only
|
||||
|
||||
- **Decision**: Treat `docs/ui/operator-ux-surface-standards.md` as a reference-only alignment target and touch it only if one minimal cross-reference is needed.
|
||||
- **Rationale**: The workflow changes belong in `.specify/` and the feature-local artifacts. Duplicating them into operator-UX standards would reintroduce drift between rule source, workflow, and supporting prose.
|
||||
- **Alternatives considered**:
|
||||
- Expand the operator-UX standards doc with full guardrail mechanics: rejected because it would become a second operational handbook.
|
||||
- Ignore the doc entirely: rejected because a minimal pointer may still be needed to keep terminology aligned.
|
||||
|
||||
## Decision 9: Validate both low-friction and high-risk paths
|
||||
|
||||
- **Decision**: Validate the workflow against one low-impact docs-only scenario and multiple high-risk representative cases grounded in Specs 196 through 200.
|
||||
- **Rationale**: The low-impact scenario proves the workflow does not overharden ordinary documentation changes. The representative drift cases prove that the new prompts and checklist language can classify the problem classes the feature is meant to stop.
|
||||
- **Alternatives considered**:
|
||||
- Validate only a high-risk case: rejected because it would not prove that the low-impact path stays lightweight.
|
||||
- Validate only hypothetical examples: rejected because the repo already has real cases and vocabulary anchors that should be reused.
|
||||
|
||||
## Validation Scenario Set
|
||||
|
||||
| Scenario | Source artifact | Expected guardrail result |
|
||||
|---|---|---|
|
||||
| Low-impact docs-only workflow change | `.specify/templates/checklist-template.md` and `.specify/README.md` | `N/A` path remains acceptable, no fake runtime obligations, explicit `keep` outcome remains possible |
|
||||
| Fake-native hard-signal case | `specs/196-hard-filament-nativity-cleanup/` | hard technical signal and blocker unless a bounded exception exists |
|
||||
| Shared-family host-drift case | `specs/197-shared-detail-contract/` | review-mandatory classification with host/core boundary check and exception spread control |
|
||||
| State-layer confusion case | `specs/198-monitoring-page-state/` and `specs/199-global-context-shell-contract/` | review-mandatory classification that names shell/page/detail ownership explicitly |
|
||||
| Legitimate special-case surface | `specs/200-filament-surface-rules/` representative exception model | documentation-required exception rather than default violation |
|
||||
|
||||
## Implementation Adjustment Note
|
||||
|
||||
- The narrowest rollout for Spec 201 is feature-local guardrail design plus workflow-surface updates. It should not pull in runtime code, root-level onboarding docs, or CI policy unless the implementation proves that a smaller workflow surface is insufficient.
|
||||
|
||||
## Canonical Repository Signal Catalog
|
||||
|
||||
| Signal ID | Problem class | First-pass handling mode | Exception / review path | Future promotion rule |
|
||||
|---|---|---|---|---|
|
||||
| `SIG-001` | Fake-native control or page-body interaction disguised as native Filament behavior | `hard-stop-candidate` | Named bounded exception only; otherwise reviewer stops the change | Eligible for hard CI blocking only after report-first use stays low-noise across real repo cases |
|
||||
| `SIG-002` | GET-form or request-driven page-body state outside the documented shell or list/filter contract | `review-mandatory` | Review must classify whether the state belongs to shell, page, detail, or URL/query ownership | Promote only if future false positives stay low and the state-owner vocabulary remains stable |
|
||||
| `SIG-003` | Shared-family host fork or duplicate partial drift | `review-mandatory` | Fold back into the shared contract or document a bounded host exception with spread control | Future linting is deferred until the shared-family boundaries remain stable across multiple cases |
|
||||
| `SIG-004` | Shell or context resolution logic leaking into presentation partials or page-local helpers | `review-mandatory` | Review must prove shell ownership and keep page/detail surfaces from becoming hidden context engines | Stay report-first until the repo has a proven shell regression pattern worth hardening |
|
||||
| `SIG-005` | Hand-rolled simple overview or status UI where native Filament widgets or shared primitives should remain primary | `report-only` | Review decides whether this is a real special visualization or just avoidable custom surface drift | Never hard-stop automatically without a later spec proving the false-positive rate is acceptably low |
|
||||
|
||||
## Canonical Test Guardrail Matrix
|
||||
|
||||
| Surface class | Trigger | Required proof types | Manual smoke expectation | Relief rule |
|
||||
|---|---|---|---|---|
|
||||
| `standard-native-filament` | Standard Resource/Page/RelationManager work with no special shared-family, shell, or exception contract | Ordinary feature coverage only | Optional normal UI smoke only | This is the explicit relief rule: no extra bespoke guardrail proof is required |
|
||||
| `shared-detail-family` | Reused detail micro-UI or host/core contract shared across multiple surfaces | `functional-core` | Verify host/core boundaries and one representative host rendering | Add exception coverage only if a host intentionally diverges |
|
||||
| `monitoring-state-page` | Monitoring/governance page with its own state contract | `functional-core`, `state-contract` | Verify empty, loaded, and conflict states remain legible | Use the narrowest honest lane; no browser expansion by default |
|
||||
| `global-context-shell` | Shared shell or context-recovery surface | `state-contract`, `exception-fallback` | Verify valid scope, invalid scope, and recovery/fallback clarity | Manual smoke stays required because shell truth crosses many pages |
|
||||
| `exception-coded-surface` | Legitimate custom or special visualization that relaxes a default rule | `functional-core`, `exception-fallback` | Verify the bounded exception and the preserved standardized parts | Close-out must explain why `standard-native-filament` relief does not apply |
|
||||
|
||||
## First-Pass Automation Deferrals
|
||||
|
||||
- Grep or lint rules for `SIG-001` through `SIG-005` stay report-first only; no CI hard-stop is introduced in this rollout.
|
||||
- No PR bot or generated comment is added to enforce the active feature PR close-out entry.
|
||||
- No repo-wide scanner auto-detects shared-family host forks until false-positive escape paths are proven in practice.
|
||||
- No automatic promotion from `review-mandatory` to blocking occurs without a later spec that reviews noise, misses, and ownership burden explicitly.
|
||||
266
specs/201-enforcement-review-guardrails/spec.md
Normal file
266
specs/201-enforcement-review-guardrails/spec.md
Normal file
@ -0,0 +1,266 @@
|
||||
# Feature Specification: Enforcement & Review Guardrails
|
||||
|
||||
**Feature Branch**: `[201-enforcement-review-guardrails]`
|
||||
**Created**: 2026-04-18
|
||||
**Status**: Proposed
|
||||
**Input**: User description: "Spec 201 - operationalize the UI/UX constitution rules from Spec 200 into repeatable review, repository, test, exception, and workflow guardrails."
|
||||
|
||||
## Spec Candidate Check *(mandatory - SPEC-GATE-001)*
|
||||
|
||||
- **Problem**: Spec 200 now defines the UI and surface rules, but the repo still lacks repeatable day-to-day mechanisms that force reviewers and authors to detect new drift early, classify legitimate exceptions, and keep shared-family or state-layer mistakes from re-entering under delivery pressure.
|
||||
- **Today's failure**: The team can agree with the constitution in principle, yet fake-native controls, hidden exceptions, host drift, and shell or page or detail state confusion can still merge because there is no mandatory review language, no shared signal catalog, and no explicit workflow contract for when extra tests or exception notes are required.
|
||||
- **User-visible improvement**: Operators keep a more consistent, trustworthy admin product over time because future UI and surface work is checked against explicit guardrails before drift becomes another cleanup project.
|
||||
- **Smallest enterprise-capable version**: Define a bounded guardrail catalog, a mandatory review checklist, a repository signal catalog, a test-guardrail matrix, an exception workflow, and spec-flow integration that operationalize Spec 200 without doing another cleanup sweep or inventing heavyweight tooling.
|
||||
- **Explicit non-goals**: No runtime cleanup of existing surfaces, no new product features, no broad refactor of page or shell logic, no separate governance process outside the existing spec workflow, and no attempt to turn every design judgment into a hard technical gate.
|
||||
- **Permanent complexity imported**: A finite guardrail vocabulary, review outcome classes, technical signal classes, exception documentation requirements, and close-out expectations for UI and surface work.
|
||||
- **Why now**: Spec 200 is the normative rule set now. If guardrails do not follow immediately, the same drift classes will return while adjacent specs are still being implemented.
|
||||
- **Why not local**: Ad-hoc PR comments or personal grep habits cannot create consistent review outcomes, visible exception boundaries, or reusable test-trigger rules across the repo.
|
||||
- **Approval class**: Cleanup
|
||||
- **Red flags triggered**: Foundation-sounding governance work and new classification vocabulary. Defense: the scope is explicitly limited to operationalizing existing Spec 200 rules, keeps enforcement proportional, and avoids adding a second parallel framework or mandatory toolchain.
|
||||
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexität: 1 | Produktnähe: 1 | Wiederverwendung: 2 | **Gesamt: 10/12**
|
||||
- **Decision**: approve
|
||||
|
||||
## Spec Scope Fields *(mandatory)*
|
||||
|
||||
- **Scope**: workspace
|
||||
- **Primary Routes**: No end-user HTTP routes are changed. The affected surfaces are repository-owned review artifacts, spec and plan and task expectations, close-out notes, and any lightweight repository checks that expose UI and surface drift before merge.
|
||||
- **Data Ownership**: Workspace-owned governance artifacts only. No tenant-owned tables, runtime records, or new persisted product-truth entities are introduced.
|
||||
- **RBAC**: No runtime authorization behavior changes. The affected actors are contributors and reviewers working on operator-facing surfaces inside the existing spec-driven workflow.
|
||||
|
||||
## Proportionality Review *(mandatory when structural complexity is introduced)*
|
||||
|
||||
- **New source of truth?**: no, this spec operationalizes the existing rule truth from Spec 200 rather than creating a second normative source
|
||||
- **New persisted entity/table/artifact?**: yes, but only repository-owned artifacts such as guardrail catalogs, review checklists, and close-out guidance
|
||||
- **New abstraction?**: yes, a bounded guardrail taxonomy that classifies review signals, repository signals, test triggers, and documented exceptions
|
||||
- **New enum/state/reason family?**: yes, because guardrail classes and review outcome classes become named categories used across future UI and surface work
|
||||
- **New cross-domain UI framework/taxonomy?**: yes, but only to translate already-approved surface rules into one finite operational model
|
||||
- **Current operator problem**: The product already knows what good surface behavior should look like, but the delivery workflow still lacks a reliable way to stop new fake-native patterns, hidden exceptions, shared-family drift, and state-layer confusion before they ship.
|
||||
- **Existing structure is insufficient because**: Spec 200 defines the rules, but it does not yet say which questions are mandatory in review, which patterns are technically signalable, which surface classes must trigger extra tests, or how legitimate exceptions remain visible and bounded.
|
||||
- **Narrowest correct implementation**: Add one operational guardrail layer that maps the existing constitution to review, repository, test, exception, and workflow expectations. Do not create cleanup implementation, hard CI enforcement for every case, or a parallel approval bureaucracy.
|
||||
- **Ownership cost**: Maintainers must keep the guardrail catalog aligned with Spec 200, preserve the shared vocabulary in future specs and PRs, and maintain any lightweight signal definitions or close-out expectations that result from this work.
|
||||
- **Alternative intentionally rejected**: Rely on tribal review knowledge, add only local PR templates, or attempt a fully automated hard-gate system before the repo has a bounded and reviewable guardrail model.
|
||||
- **Release truth**: current-release workflow truth needed now so Spec 200 remains effective in daily implementation and review
|
||||
|
||||
## Problem Statement
|
||||
|
||||
The audit trail behind Specs 196 through 200 exposed the same failure pattern repeatedly: the repo can name the drift, but it cannot yet stop it early enough.
|
||||
|
||||
The known problem classes are already concrete:
|
||||
|
||||
- fake-native controls and GET-form interactions inside Filament surfaces
|
||||
- Blade-request-driven UI state in page bodies and adjacent UI surfaces
|
||||
- hand-rolled simple overviews where standard primitives should remain primary
|
||||
- shared detail families that drift by host-specific fork instead of one shared contract
|
||||
- page-state, shell-state, and detail-state ownership collapsing into each other
|
||||
- legitimate custom exceptions that remain invisible and therefore expand quietly
|
||||
|
||||
Without operational guardrails, the constitution is vulnerable to the usual failure mode: agreement in principle, drift in practice, then another cleanup wave later.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Depends on Spec 200 - Filament Surface Rules as the normative rule source this spec operationalizes.
|
||||
- Uses the proven drift cases from Spec 196 - Hard Filament Nativity Cleanup as the clearest examples of fake-native and request-driven UI problems.
|
||||
- Uses the shared-family and host-drift lessons from Spec 197 - Shared Detail Contract.
|
||||
- Uses the page-state ownership lessons from Spec 198 - Monitoring Page State.
|
||||
- Uses the shell and context ownership lessons from Spec 199 - Global Context Shell Contract.
|
||||
- Does not replace or reopen the cleanup and implementation scope of any adjacent spec.
|
||||
|
||||
## Goals
|
||||
|
||||
- Translate the Spec 200 rule set into repeatable review guardrails, repository signals, test triggers, exception handling, and workflow expectations.
|
||||
- Make the team distinguish clearly between what is review-only, what is technically signalable, what requires extra testing, and what is allowed only through visible exception handling.
|
||||
- Make legitimate exceptions visible, bounded, and reviewable instead of accidental precedent.
|
||||
- Keep the guardrails proportionate so they help contributors before merge without creating tool-driven theater.
|
||||
- Integrate the guardrails into the existing spec-driven workflow rather than creating a competing process.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Cleaning up existing UI and surface violations as part of this spec.
|
||||
- Building heavy custom linting, CI policy, or repo tooling beyond what is needed for lightweight early warning.
|
||||
- Forcing hard technical gates on design judgments that only human review can assess reliably.
|
||||
- Inventing new UI rules beyond Spec 200.
|
||||
- Turning legitimate custom surfaces into forbidden territory.
|
||||
|
||||
## Assumptions
|
||||
|
||||
- Spec 200 remains the normative rule source, and Spec 201 only operationalizes it.
|
||||
- Some design decisions will remain structured human judgment even after this work is complete.
|
||||
- Legitimate custom surfaces, special visualizations, and bounded local state will continue to exist, so the exception path must be first-class rather than grudging.
|
||||
- The existing spec and plan and task and close-out workflow remains the primary delivery process and should absorb these guardrails directly.
|
||||
|
||||
## Guardrail Classification Model
|
||||
|
||||
### Guardrail Classes
|
||||
|
||||
- **Hard Technical Signals**: reliable warning patterns that can expose likely drift early
|
||||
- **Structured Review Signals**: questions that must be answered in review even when no technical pattern can decide the case alone
|
||||
- **Required Test Signals**: surface classes that must trigger additional tests or smoke expectations
|
||||
- **Exception Signals**: cases where deviation can be legitimate only if it is visible, bounded, and justified
|
||||
|
||||
### Handling Modes
|
||||
|
||||
- **Hard-stop candidate**: drift pattern that may become blocking once the repo has an approved exception path
|
||||
- **Review-mandatory**: pattern or surface type that always needs explicit human classification
|
||||
- **Exception-required**: case that is allowed only with a documented exception model
|
||||
- **Report-only**: lightweight visibility signal that supports trend review without default blocking
|
||||
|
||||
### Review Outcome Classes
|
||||
|
||||
- **Blocker**: the change cannot proceed until the guardrail issue is corrected or explicitly split
|
||||
- **Strong warning**: the change may proceed only if the remaining guardrail risk is acknowledged and resolved in the active workflow
|
||||
- **Documentation-required exception**: the change is acceptable only once the named exception path is completed and bounded
|
||||
- **Acceptable special case**: the change remains legitimate without additional guardrail escalation beyond ordinary documentation
|
||||
|
||||
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
|
||||
|
||||
- **Test purpose / classification**: N/A
|
||||
- **Validation lane(s)**: N/A
|
||||
- **Why this classification and these lanes are sufficient**: This feature changes review and specification artifacts, not runtime behavior. Validation is document-based and checks completeness, traceability, clarity, and workflow fit.
|
||||
- **New or expanded test families**: none
|
||||
- **Fixture / helper cost impact**: none
|
||||
- **Heavy-family visibility / justification**: none
|
||||
- **Reviewer handoff**: Reviewers must confirm that each major Spec 200 rule family maps to explicit guardrail behavior, that no second governance framework is introduced, and that the examples remain grounded in the real drift cases from Specs 196 through 200.
|
||||
- **Budget / baseline / trend impact**: none
|
||||
- **Escalation needed**: none
|
||||
- **Planned validation commands**: N/A
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - Reviewers Classify Drift Early (Priority: P1)
|
||||
|
||||
As a reviewer, I want a mandatory UI and surface checklist so I can detect fake-native patterns, host drift, hidden exceptions, and shell or page or detail state confusion before merge.
|
||||
|
||||
**Why this priority**: Review is the earliest practical control point for most new surface work. If the checklist cannot classify obvious drift early, the rest of the guardrail model will be too weak to matter.
|
||||
|
||||
**Independent Test**: Can be fully tested by walking a reviewer through a fake-native case, a host-drift case, and a shell or page or detail state confusion case and confirming that the reviewer can classify each one using only the documented checklist, review outcome classes, and handling modes.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a Filament-looking surface that introduces plain controls or a GET-form interaction for a core page-body behavior, **When** the reviewer applies the guardrail checklist, **Then** the reviewer can classify the change as a blocker or strong warning with an explicit handling mode rather than a neutral custom implementation.
|
||||
2. **Given** a repeated shared detail surface starts diverging by host-specific variation, **When** the reviewer applies the guardrails, **Then** the reviewer can identify whether the change belongs inside the shared family contract or must be treated as a documented exception.
|
||||
3. **Given** a page mixes shell context, page interaction state, and detail viewer state without clear ownership, **When** the reviewer applies the checklist, **Then** the reviewer can name the state-layer problem explicitly and stop the change from being treated as harmless implementation detail.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - Authors Declare Native Vs Custom Up Front (Priority: P1)
|
||||
|
||||
As an author planning new UI or surface work, I want the spec workflow to force native versus custom classification, state-layer classification, shared-family relevance, and exception need so I can design the surface correctly before implementation starts.
|
||||
|
||||
**Why this priority**: The guardrails only prevent drift if they shape work before code review. Up-front classification keeps the repo from discovering surface-contract problems only after implementation is already expensive to unwind.
|
||||
|
||||
**Independent Test**: Can be fully tested by drafting a new UI or surface spec and confirming that the planning artifacts capture the required classifications, expected test depth, and exception need without adding a second workflow.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a new complex surface is proposed, **When** the author fills the relevant spec and plan and task fields, **Then** the work item records whether the surface is native, custom, shared-family relevant, or exception-bound and which state layers are involved.
|
||||
2. **Given** a legitimate special visualization or bounded local-state surface is needed, **When** the author declares the exception, **Then** the planning artifacts capture why default primitives do not fit, how the exception remains bounded, and which standardized parts still stay intact.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - Maintainers Get Proportionate Signals And Test Triggers (Priority: P2)
|
||||
|
||||
As a maintainer, I want a catalog of repository signals and required test triggers so likely drift becomes visible in daily work without making every custom surface a false-positive trap or a hard CI failure.
|
||||
|
||||
**Why this priority**: Repository-level visibility is the bridge between pure policy and daily implementation. Without signal and test guidance, drift will remain review-dependent and inconsistent.
|
||||
|
||||
**Independent Test**: Can be fully tested by mapping known drift patterns and surface classes to the catalog and confirming that every signal has a handling mode and every relevant surface class has a declared test depth.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a repository signal matches a likely fake-native or shell-resolution pattern, **When** the maintainer consults the catalog, **Then** the handling mode is explicit as report-only, review-mandatory, exception-required, or hard-stop candidate.
|
||||
2. **Given** a new shared detail family, monitoring page with its own state contract, context shell touchpoint, or exception-coded surface is introduced, **When** the work is prepared for merge, **Then** the required tests and smoke expectations are visible and standard native surfaces are not burdened with unnecessary bespoke coverage.
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- A technical signal flags a legitimate custom surface; the exception path must prevent noisy permanent false positives.
|
||||
- A one-off exception starts spreading to additional hosts or surfaces; the guardrails must require renewed review instead of treating the first approval as general precedent.
|
||||
- A standard native Filament surface changes without introducing a new state contract; the guardrails must avoid demanding special smoke coverage that adds friction without value.
|
||||
- Spec 200 evolves later; the guardrail catalog must remain explicitly traceable to the rule it operationalizes so the workflow does not drift from the constitution.
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
**Constitution alignment:** This feature adds no product runtime behavior, no Microsoft Graph behavior, no new authorization plane, and no new operator-facing page. It does add repository and workflow guardrails for operator-facing UI and surface work, so the resulting review vocabulary, signal catalog, exception model, and close-out expectations must remain explicit, bounded, and traceable to Spec 200.
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001 Guardrail Catalog**: The implementation MUST map every relevant Spec 200 rule family to one or more of the following operational classes: review guardrail, repository signal, required test signal, exception signal, or workflow integration requirement.
|
||||
- **FR-002 Mandatory Review Checklist**: The implementation MUST provide one binding review checklist for UI and surface work that reviewers can apply without inventing local terminology.
|
||||
- **FR-003 Checklist Questions**: The review checklist MUST require explicit answers for at least these questions: whether the surface is native-by-default or legitimately custom, whether expected native primitives are used, whether any deviation is an explicit exception, whether the surface belongs to a shared detail family or one-off host, whether host drift exists or is emerging, which state layers are present, how URL or query state is classified, whether competing interaction models exist, whether the surface is a simple overview or a true special visualization, and whether an existing exception is being expanded quietly.
|
||||
- **FR-004 Review Outcomes**: The review checklist MUST classify findings as blocker, strong warning, documentation-required exception, or acceptable special case.
|
||||
- **FR-005 Merge Readiness Rule**: New complex surfaces MUST NOT be treated as ready for merge until their guardrail classification is explicit in review and associated planning artifacts.
|
||||
- **FR-006 Repository Signal Catalog**: The implementation MUST define repository-level warning signals for the main fake-native and drift-prone patterns, including GET-form interactions inside Filament surfaces, plain controls masquerading as native controls, request-driven UI state near page-body surfaces, hand-built simple overviews where standard primitives should remain primary, host-specific forks of known shared families, and shell-context resolution logic leaking into presentation partials.
|
||||
- **FR-007 Signal Handling Modes**: Every repository signal MUST declare whether it is report-only, review-mandatory, exception-required, or a hard-stop candidate, and whether the signal is eligible for later promotion to blocking.
|
||||
- **FR-008 False-Positive Control**: Repository guardrails MUST include an explicit exception or review path so legitimate custom surfaces do not become permanently noisy false positives.
|
||||
- **FR-009 Test Guardrail Matrix**: The implementation MUST define which special surface classes require extra test depth, including shared detail micro-UI families, monitoring or governance pages with their own state contracts, global context shell surfaces, and legitimate exception surfaces with intentional special contracts.
|
||||
- **FR-010 Required Test Types**: For each special surface class, the guardrail model MUST state whether functional core interaction tests, state-contract tests, exception or fallback behavior tests, and manual smoke expectations are required.
|
||||
- **FR-011 Standard Surface Relief**: The guardrail model MUST state when standard native Filament surfaces do not need extra special tests beyond normal feature coverage.
|
||||
- **FR-012 Exception Model**: Every legitimate exception MUST document which default rule it breaks or does not fully satisfy, why native or default behavior is insufficient, how the exception remains bounded, which parts remain standardized, and which follow-on risks are consciously accepted.
|
||||
- **FR-013 Exception Spread Control**: Exceptions MUST NOT extend silently to additional hosts or surfaces; any expansion requires renewed explicit review.
|
||||
- **FR-014 Workflow Integration**: The implementation MUST integrate guardrail expectations into spec creation, planning, tasks, implementation review, definition-of-done checks, and follow-up exception documentation for UI and surface-relevant work.
|
||||
- **FR-015 Planning Visibility**: UI and surface-relevant specs MUST capture native versus custom classification, state-layer classification, shared-family relevance, and exception need in a way that is visible before implementation begins.
|
||||
- **FR-016 Close-Out Visibility**: Completion notes for relevant work MUST record which guardrail class was triggered, whether an exception was required, and which tests or smoke checks were added or intentionally not needed in the active feature PR close-out entry.
|
||||
- **FR-017 Guardrail Matrix**: The implementation MUST publish a rule matrix that distinguishes hard-stop candidates, review-mandatory cases, exception-required cases, and report-only cases with representative examples from Specs 196 through 200.
|
||||
- **FR-018 Deliverable Set**: The implementation MUST produce a guardrail catalog, a UI and surface review checklist, a technical signal catalog, a test-guardrail catalog, an exception workflow, workflow integration notes, and close-out documentation describing what remains review-only versus technically signalable.
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- **NFR-001 Early Warning**: The guardrails must expose likely drift early enough that review can stop it before another cleanup pass becomes necessary.
|
||||
- **NFR-002 Proportionality**: Only patterns that are reliably observable should become repository signals or future hard-stop candidates; judgment-heavy questions must remain structured review instead of fake precision.
|
||||
- **NFR-003 Workflow Fit**: The guardrails must strengthen the existing spec-driven workflow instead of creating a second process that contributors must learn separately, keep the low-impact path completable in under 1 minute, keep a representative guarded review completable in under 3 minutes, and avoid duplicating the same classification question unnecessarily across spec, plan, task, review, and close-out surfaces.
|
||||
- **NFR-004 Traceability**: Every guardrail class and example must remain traceable to the governing rules from Spec 200 and its supporting Specs 196 through 199.
|
||||
|
||||
### Key Entities *(include if feature involves data)*
|
||||
|
||||
- **Guardrail Catalog**: The authoritative operational mapping from Spec 200 rules to review expectations, repository signals, test triggers, exception handling, and workflow touchpoints.
|
||||
- **Review Checklist**: The binding question set that reviewers and authors use to classify UI and surface work consistently.
|
||||
- **Repository Signal**: A documented red-flag pattern that can expose likely drift and carries a defined handling mode.
|
||||
- **Test Guardrail Profile**: The required test and smoke depth attached to a surface class when its contract is more complex than standard native surfaces.
|
||||
- **Exception Record**: The visible justification that bounds a legitimate deviation from the default surface rules and prevents silent expansion.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: Every targeted drift class from Spec 200 has at least one documented mapping to review guardrails, repository signals, required test handling, or exception handling.
|
||||
- **SC-002**: Reviewers can classify the representative cases of fake-native dependency edges, legitimate special visualization, shared-family host drift, and shell or page or detail state confusion into the defined review outcome classes and handling modes without inventing new categories.
|
||||
- **SC-003**: All UI and surface-relevant work items created after rollout include explicit native or custom classification, state-layer classification, shared-family relevance, and exception need in their planning artifacts.
|
||||
- **SC-004**: Every documented exception includes all required justification fields and an explicit boundary that prevents silent reuse as general precedent.
|
||||
- **SC-005**: Every repository signal in the catalog has an assigned handling mode and at least one documented review or exception path, so no signal remains an ownerless warning.
|
||||
- **SC-006**: Reviewers can determine in one pass which special surface classes require additional tests and which standard native surfaces do not.
|
||||
- **SC-007**: A low-impact docs-only workflow path remains completable in under 1 minute, a representative guarded review remains completable in under 3 minutes, and neither path requires contributors to answer the same classification question redundantly across workflow surfaces.
|
||||
|
||||
## Validation Notes
|
||||
|
||||
### Reviewer Workflow Validation
|
||||
|
||||
| Scenario | Source artifact | Outcome class | Handling mode | Workflow outcome | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| Low-impact docs-only path | `.specify/templates/checklist-template.md` + `.specify/README.md` | `acceptable-special-case` | `report-only` | `keep` | One `N/A` note stayed sufficient; no fake UI-surface prose was required |
|
||||
| Fake-native hard signal | `specs/196-hard-filament-nativity-cleanup/spec.md` | `blocker` | `hard-stop-candidate` | `reject-or-split` | Fake-native drift still reaches the strongest guardrail outcome cleanly |
|
||||
| Shared-family host drift | `specs/197-shared-detail-contract/spec.md` | `strong-warning` | `review-mandatory` | `document-in-feature` | The workflow stops host drift without inventing new categories |
|
||||
| State-layer confusion | `specs/198-monitoring-page-state/spec.md` + `specs/199-global-context-shell-contract/spec.md` | `strong-warning` | `review-mandatory` | `document-in-feature` | Shell/page/detail ownership now resolves through one fixed question set |
|
||||
| Legitimate special visualization | `specs/200-filament-surface-rules/spec.md` | `documentation-required-exception` | `exception-required` | `document-in-feature` | Legitimate special cases remain allowed only with bounded exception notes |
|
||||
|
||||
- Representative guarded review elapsed time: `02:34`
|
||||
- Duplicate-question note: the final workflow asks for native/custom, shared-family, and state ownership once in the spec, then reuses that classification in plan, tasks, checklist, and close-out.
|
||||
|
||||
### Authoring Workflow Validation
|
||||
|
||||
| Scenario | Source artifact | Outcome | Elapsed time | Notes |
|
||||
|---|---|---|---|---|
|
||||
| Low-impact docs-only flow | `.specify/templates/checklist-template.md` + `.specify/README.md` | `keep` | `00:48` | Low-impact `N/A` remains fast and does not fabricate runtime obligations |
|
||||
| Surface-changing authoring flow | `specs/200-filament-surface-rules/spec.md` | `document-in-feature` | `01:51` | Native/custom classification, handling modes, and proof depth stay explicit without adding a second workflow |
|
||||
|
||||
### Signal And Test-Trigger Validation
|
||||
|
||||
| Drift / surface class | Handling mode | Required proof profile | Close-out expectation |
|
||||
|---|---|---|---|
|
||||
| Fake-native hard signal | `hard-stop-candidate` | Special proof only if a bounded exception is claimed | Close-out notes are required only when an exception is still in play |
|
||||
| Shared-family host drift | `review-mandatory` | `shared-detail-family` | Record host/core boundary and any exception spread control |
|
||||
| Monitoring or shell state-layer confusion | `review-mandatory` | `monitoring-state-page` or `global-context-shell` | Record state-owner proof and any required smoke |
|
||||
| Legitimate special visualization | `exception-required` | `exception-coded-surface` | Record bounded exception, preserved standards, and manual smoke |
|
||||
| Standard native Filament surface | `report-only` | `standard-native-filament` relief | Record ordinary feature coverage only; no bespoke guardrail proof required |
|
||||
|
||||
- Standard-native relief rule: standard native Filament work does not need extra bespoke guardrail tests unless it introduces a shared-family contract, shell/page/detail state ownership risk, or a bounded exception.
|
||||
- Active feature PR close-out entry name: `Guardrail / Exception / Smoke Coverage`
|
||||
- First-pass automation deferrals remain explicit: report-first signals only, no CI hard-stop, no PR bot, no auto-promotion of review-mandatory cases.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user