13. Operations Capstone
The capstone joins Directory, Access, Workflow, and Ops into one effective domain program while keeping the Ops Flutter application independently deployable.
1. Define the module graph
dart
final directoryModule = Module(
name: 'directory',
title: 'Directory',
schema: 'directory',
version: '1.0.0',
entities: [companyEntity, siteEntity, userEntity],
exports: const [
EntityExport('company'),
EntityExport('site'),
EntityExport('user'),
],
);
final accessModule = Module(
name: 'access',
title: 'Access',
schema: 'access',
version: '1.0.0',
entities: [roleEntity, permissionEntity, roleAssignmentEntity],
dependsOn: const ['directory'],
exports: const [
EntityExport('role'),
EntityExport('role_assignment'),
],
);
final workflowModule = Module(
name: 'workflow',
title: 'Workflow',
schema: 'workflow',
version: '1.0.0',
entities: [taskEntity],
exports: const [
TriggerExport('task.lifecycle.create'),
],
);2. Declare the Ops entity
dart
final areaEntity = Entity(
name: 'area',
title: 'Area',
pluralTitle: 'Areas',
description: 'Controlled physical spaces within an operating site.',
tableName: 'areas',
schema: 'ops',
schemaType: 'ops.area',
tenantField: AreaFields.tenantId,
versioning: const EntityVersioning.revisions(),
uniqueKeys: const [
[AreaFields.tenantId, AreaFields.code],
],
db: const EntityDb(
expectedRows: DbRowScale.medium,
readPattern: DbReadPattern.runtimeList,
queryPatterns: [
QueryPattern.filter,
QueryPattern.search,
QueryPattern.sort,
],
),
seed: const EntitySeed(
targetRows: 12,
scenarios: ['draft area', 'active classified room', 'retired wash bay'],
),
ui: const EntityUI(
icon: UIIcon('location'),
routePrefix: '/areas',
list: ListUI(
layouts: [
TableUI(
columns: [
TableColumnUI(field: AreaFields.code, required: true),
TableColumnUI(field: AreaFields.name),
TableColumnUI(field: AreaFields.areaType),
TableColumnUI(
field: AreaFields.status,
appearance: UIColumnAppearance.status,
),
],
),
GridUI(
content: CardContentUI(
titleField: AreaFields.name,
subtitleField: AreaFields.code,
statusField: AreaFields.status,
),
),
],
query: QueryUI(maxSortLevels: 3, maxGroupLevels: 2),
),
editors: [
EditorUI(
name: 'area-editor',
title: 'Area',
presentation: EditorPresentation.adaptive,
form: FormUI.derived(),
),
],
details: [
DetailUI(
name: 'summary',
title: 'Summary',
kind: DetailUIKind.summary,
),
DetailUI(
name: 'history',
title: 'History',
kind: DetailUIKind.history,
),
DetailUI(
name: 'audit',
title: 'Audit',
kind: DetailUIKind.audit,
),
],
actionPlacements: [
ActionPlacementUI(
placement: ActionPlacementRef.recordHeader,
actions: [
ActionUIRef('governance.activate'),
ActionUIRef('governance.retire'),
],
),
],
),
facets: [
IdentityFacet(
fields: const [
AreaFields.code,
AreaFields.name,
AreaFields.description,
],
),
Facet(
name: 'tenancy',
fields: const [AreaFields.tenantId, AreaFields.siteId],
),
areaGovernanceFacet,
areaHierarchyFacet,
],
);The field tokens carry enum labels, reference hints, input capability, query posture, and the name-to-code derivation developed in earlier chapters.
3. Govern activation
dart
final areaGovernanceFacet = Facet(
name: 'governance',
lifecycle: Lifecycle(
stateField: AreaFields.status,
initialState: 'draft',
transitions: const [
Transition(from: 'draft', to: 'active', trigger: 'activate'),
Transition(from: 'active', to: 'retired', trigger: 'retire'),
],
),
actions: [
Action(
name: 'activate',
title: 'Activate',
consistency: ConsistencyMode.strict,
rules: const [
Rule(
id: 'ops.area.activate.grant',
kind: RuleKind.access,
condition: ActorHasGrantCondition(grant: 'ops.area.approve'),
),
Rule(
id: 'ops.area.activate.qualified',
kind: RuleKind.access,
condition: ActorQualifiedCondition(
qualificationRefs: ['training.gmp_current'],
scope: 'site',
),
),
Rule(
id: 'ops.area.activate.checklist',
kind: RuleKind.evidence,
condition: EvidencePresentCondition(
evidence: 'area_release_checklist',
),
),
],
capture: ActionCapture.signed(
evidence: [releaseChecklist],
snapshot: ContextSnapshot(
targets: [
SnapshotTarget(
name: 'policy.area_release',
sourceRef: 'policy.ops.area_release',
),
SnapshotTarget(
name: 'master.area_category',
strategy: SnapshotStrategy.value,
sourceRef: 'ops.area.category',
),
],
),
),
emits: const [
ActionEvent(
name: 'ops.area.activated',
kind: ActionEventKind.stateChanged,
),
],
),
],
);4. Assemble the Ops module
dart
final opsModule = Module(
name: 'ops',
title: 'Operations',
schema: 'ops',
version: '1.0.0',
entities: [
areaCategoryEntity,
areaEntity,
equipmentCategoryEntity,
equipmentEntity,
activityEntity,
activityArtifactEntity,
checklistTemplateEntity,
],
dependsOn: const ['directory', 'access', 'workflow'],
taskTemplates: [areaActivationReviewTask],
db: const ModuleDb(
auditMode: DbAuditMode.full,
rlsMode: DbRlsMode.enforced,
outboxMode: DbOutboxMode.schema,
retentionRef: 'gxp_10_years',
),
seed: const ModuleSeed(
datasetSize: SeedDatasetSize.validation,
realism: SeedRealism.regulated,
scenarios: [
'approved area activation',
'denied unqualified actor',
'retirement with reason and signature',
],
),
);
final pharmaBlueprint = Blueprint(
name: 'vyrun_pharma',
version: '1.0.0',
modules: [
directoryModule,
accessModule,
workflowModule,
opsModule,
],
);5. Type-check and compile
dart
final findings = BlueprintValidator.validate(pharmaBlueprint);
if (findings.isNotEmpty) {
for (final finding in findings) {
print('${finding.code} ${finding.path}: ${finding.message}');
}
throw StateError('Blueprint is invalid.');
}
final effective = pharmaBlueprint.bootstrap();
final database = DbCompiler.compile(pharmaBlueprint);
print(effective.entity('ops.area')?.originGraph);
print(database.toReport());Review generated SQL, migrations, RLS/grants, reference integrity, indexes, audit/history, runtime records, and outbox before installation.
6. Assemble app surfaces
dart
const opsAppDescriptor = AppDescriptor(
name: 'ops',
title: 'Operations',
);
final opsBlueprint = BlueprintAssembler.assemble(
name: 'ops',
title: 'Operations',
version: '1.0.0',
blueprint: pharmaBlueprint,
descriptors: const [opsAppDescriptor],
entityUI: const AppEntityUIScope.selected(
moduleRefs: ['ops'],
),
);The assembled Blueprint still sees Directory and Access entities for references and policy resolution, but exposes only Ops entity UI and routes.
7. Prove one execution end to end
For a validation scenario:
- Seed company, site, actor, grant, qualification, area category, and area.
- Request
ops.area.governance.activate. - Inspect plan and preflight.
- Attach checklist evidence and signature context.
- Execute through the runtime adapter.
- Verify the state write, action record, rule trace, evidence manifest, domain event, audit projection, and outbox effect in one commit boundary.
- Inspect the durable action sequence, event stream sequence, and effect idempotency records.
- Repeat with an unqualified actor and prove that denial is retained.
Graduation checklist
- [ ] Every user-visible field has a declared label and semantic type.
- [ ] Every reference has an explicit relationship/reference hint.
- [ ] Every server-queryable UI field is present in the server catalog.
- [ ] Every action belongs to a facet and has typed failures.
- [ ] Every lifecycle edge names a valid action.
- [ ] Cross-module access uses dependencies and exports.
- [ ] RLS and grants are installed and negatively tested.
- [ ] Required evidence is verified before commit.
- [ ] Blueprint, policy, evaluator, and configuration revisions are pinned.
- [ ] Events are facts; external consequences leave through effects/outbox.
- [ ] Denied and failed attempts are durable.
- [ ] Durable action/event sequences and idempotency records are present.
You now have the mental model to read the Vocabulary Reference as an API map rather than as a list of disconnected classes.