Files
data-meta-model/governance/processes.yaml
T
Bastien Gourdon fbc63e55d8 tbox: v1.6 ...
2026-08-05 09:14:34 +02:00

376 lines
19 KiB
YAML

# PERNOD RICARD DATA METAMODEL — T-BOX PROCESSBOOK
# =============================================================================
# SINGLE SOURCE OF TRUTH for the procedures. The BPMN 2.0 files, the Mermaid
# views and the Markdown document are GENERATED from this file (EV-008).
#
# python3 generate_bpmn.py -> bpmn/<id>.bpmn (strict, queryable)
# python3 generate_processbook_md.py -> PR_TBox_Processbook.md (Mermaid views)
#
# NODE TYPES
# start | end events
# userTask a human decides or writes; cannot be automated
# scriptTask fully automated
# callActivity invokes another procedure by its id
# gateway exclusive decision; every outgoing flow is guarded
#
# Every node may carry `rules`, the identifiers of the rulebook rules it
# enforces. That list is what makes the BPMN queryable and what feeds the
# control.procedure field back into the rulebook.
# =============================================================================
meta:
title: Pernod Ricard Data MetaModel — T-Box Processbook
version: "0.1"
status: Draft for review
date: "2026-08-03"
scope: >
Procedures for changing the vocabulary of the model. Each procedure names
the rules it enforces, the steps a person must perform, and the points at
which the work stops rather than continues.
namespace: "https://ontology.pernod-ricard.com/process/"
families:
Create: Bringing a new element into the vocabulary.
Verify: Establishing that what exists conforms.
Update: Changing an element that already exists.
Delete: Removing an element from the vocabulary.
Release: Propagating a change and publishing a version.
processes:
# ---------------------------------------------------------------------- X1
- id: X1
family: Release
name: Propagate a change and publish a version
scope: Any validated modification of the vocabulary.
trigger: A change to the vocabulary has been agreed and is ready to apply.
inputs: [the agreed change, the target version, the list of affected artifacts]
outputs: [a merged branch, a tagged version, an execution log]
note: >
The most frequently invoked procedure of the processbook: every other
procedure ends by calling it. Two of its steps are gateways rather than
checks, because an idempotence failure and a validation failure must stop
the work rather than be noted in passing.
flow:
- {id: start, type: start, name: Change agreed}
- {id: branch, type: scriptTask, name: Create the migration branch, rules: [EV-009]}
- {id: apply, type: scriptTask, name: Apply the change through an RDF parser, rules: [EV-005, EV-006]}
- {id: labels, type: scriptTask, name: Regenerate labels by derivation, rules: [TN-023]}
- {id: abox, type: scriptTask, name: Propagate to the instances, rules: [EV-009]}
- {id: shapes, type: scriptTask, name: Update the shapes and align conformsTo, rules: [EV-010]}
- {id: artifacts, type: scriptTask, name: Regenerate every derived artifact, rules: [EV-008]}
- {id: replay, type: scriptTask, name: Replay the chain a second time, rules: [EV-006, EV-007]}
- {id: idem, type: gateway, name: "Second run reports zero change?"}
- {id: verify_voc, type: callActivity, calls: R1, name: Verify the vocabulary}
- {id: verify_abox, type: callActivity, calls: R2, name: Validate the instances}
- {id: log, type: scriptTask, name: Write the execution log for this attempt, rules: [EV-015]}
- {id: clean, type: gateway, name: "Any violation?"}
- {id: decide, type: userTask, name: Correct or abandon, rules: [EV-004, EV-007]}
- {id: drop, type: scriptTask, name: Destroy the branch and restart from the published version, rules: [EV-007]}
- {id: review, type: userTask, name: Review the merge, rules: [EV-004, EV-009]}
- {id: merge, type: scriptTask, name: Merge as a block, tag, push the tag separately, rules: [EV-009]}
- {id: bump, type: scriptTask, name: Increment the ontology version, rules: [EV-014]}
- {id: end, type: end, name: Version published}
- {id: aborted, type: end, name: Change abandoned}
flows:
- {from: start, to: branch}
- {from: branch, to: apply}
- {from: apply, to: labels}
- {from: labels, to: abox}
- {from: abox, to: shapes}
- {from: shapes, to: artifacts}
- {from: artifacts, to: replay}
- {from: replay, to: idem}
- {from: idem, to: verify_voc, condition: "zero change"}
- {from: idem, to: decide, condition: "the chain is not replayable"}
- {from: verify_voc, to: verify_abox}
- {from: verify_abox, to: log}
- {from: log, to: clean}
- {from: clean, to: review, condition: "none"}
- {from: clean, to: decide, condition: "at least one"}
- {from: decide, to: apply, condition: correct}
- {from: decide, to: drop, condition: abandon}
- {from: drop, to: aborted}
- {from: review, to: merge}
- {from: merge, to: bump}
- {from: bump, to: end}
# ---------------------------------------------------------------------- C1
- id: C1
family: Create
name: Create a term
scope: class | relation | attribute | annotation
parameter: >
The nature of the term selects the naming rules applied at the naming step:
a class takes TN-001, TN-002, TN-004, TN-005, TN-006, TN-007 and TN-027; a
relation takes TN-001, TN-002, TN-009, TN-010, TN-011 and TN-028; an
attribute takes TN-001, TN-002, TN-004, TN-012, TN-013, TN-014 and TN-015;
an annotation takes TN-001, TN-002 and TN-003.
trigger: A missing concept, edge or field has been identified.
inputs: [the intended meaning, the nature, the target layer, the provenance, the parent or the domain and range]
outputs: [a declared term, a published version]
note: >
The first step has no tool today. Checking that no existing term already
covers the need is the semantic uniqueness control that SHACL cannot
perform, and it stays a human task until R3 exists.
flow:
- {id: start, type: start, name: Need identified}
- {id: unique, type: userTask, name: Check that no existing term covers the need}
- {id: exists, type: gateway, name: "A term already covers it?"}
- {id: reuse, type: end, name: Reuse the existing term}
- {id: name, type: scriptTask, name: Name the term according to its nature, rules: [TN-001, TN-002, TN-003, TN-004, TN-005, TN-006, TN-009, TN-010, TN-011, TN-012, TN-013, TN-014, TN-015, TN-028]}
- {id: label, type: scriptTask, name: Derive the label, rules: [TN-018, TN-019, TN-020, TN-021, TN-023]}
- {id: short, type: userTask, name: Decide the short label and the acronym, rules: [TN-022]}
- {id: comment, type: userTask, name: Write the comment, rules: [TN-024]}
- {id: declare, type: scriptTask, name: Declare typing, provenance and attachment, rules: [TN-007, TN-025, TN-026, TN-027]}
- {id: validate, type: userTask, name: Validate before applying, rules: [EV-004]}
- {id: check, type: callActivity, calls: R1, name: Verify against the rulebook}
- {id: release, type: callActivity, calls: X1, name: Propagate and publish}
- {id: end, type: end, name: Term available}
flows:
- {from: start, to: unique}
- {from: unique, to: exists}
- {from: exists, to: reuse, condition: "yes"}
- {from: exists, to: name, condition: "no"}
- {from: name, to: label}
- {from: label, to: short}
- {from: short, to: comment}
- {from: comment, to: declare}
- {from: declare, to: validate}
- {from: validate, to: check}
- {from: check, to: release}
- {from: release, to: end}
# ---------------------------------------------------------------------- C2
- id: C2
family: Create
name: Create a controlled value
scope: typed individual | literal
parameter: >
The form is not a style choice but the outcome of the first decision. The
two branches have different consequences: the literal branch edits the
shapes and therefore the vocabulary, so it falls under the version freeze;
the individual branch touches nothing else.
trigger: A new value is needed in a controlled set.
inputs: [the value, the set it belongs to, whether a domain may add others]
outputs: [a declared value, a published version]
flow:
- {id: start, type: start, name: New value needed}
- {id: decide, type: userTask, name: "Must it be defined, owned or extended by a domain?", rules: [TN-016]}
- {id: form, type: gateway, name: "Which form?"}
- {id: indiv, type: scriptTask, name: Create the typed individual and derive its label, rules: [TN-001, TN-018, TN-022]}
- {id: literal, type: scriptTask, name: Write the literal in upper snake case, rules: [TN-017]}
- {id: shape, type: callActivity, calls: C3, name: Extend the closed list in the shapes}
- {id: validate, type: userTask, name: Validate before applying, rules: [EV-004]}
- {id: check, type: callActivity, calls: R1, name: Verify against the rulebook}
- {id: release, type: callActivity, calls: X1, name: Propagate and publish}
- {id: end, type: end, name: Value available}
flows:
- {from: start, to: decide}
- {from: decide, to: form}
- {from: form, to: indiv, condition: "extensible by a domain"}
- {from: form, to: literal, condition: "closed governance state"}
- {from: indiv, to: validate}
- {from: literal, to: shape}
- {from: shape, to: validate}
- {from: validate, to: check}
- {from: check, to: release}
- {from: release, to: end}
# ---------------------------------------------------------------------- C3
- id: C3
family: Create
name: Create a shape
scope: Any SHACL constraint added to the validation set.
trigger: A rule needs an executor, or a new class enters the validation perimeter.
inputs: [the rule to enforce, the target class or property]
outputs: [a shape, a rulebook entry naming it as executor]
note: >
The step that is forgotten is the last one before release: recording the
shape as the executor of its rule. Without it a rule stays declared
blocking with nothing enforcing it, which EV-011 forbids.
flow:
- {id: start, type: start, name: Constraint needed}
- {id: write, type: userTask, name: Write the constraint}
- {id: pattern, type: scriptTask, name: Check that every pattern is expressed positively, rules: [EV-013]}
- {id: portable, type: gateway, name: "Expressible within the specification?"}
- {id: demote, type: userTask, name: Move the rule to the script tier, rules: [EV-012]}
- {id: conforms, type: scriptTask, name: Align the declared ontology version, rules: [EV-010]}
- {id: test, type: callActivity, calls: R2, name: Test against real instances}
- {id: record, type: userTask, name: Record the shape as the executor of its rule, rules: [EV-011, EV-012]}
- {id: release, type: callActivity, calls: X1, name: Propagate and publish}
- {id: end, type: end, name: Shape in force}
flows:
- {from: start, to: write}
- {from: write, to: pattern}
- {from: pattern, to: portable}
- {from: portable, to: conforms, condition: "yes"}
- {from: portable, to: demote, condition: "no"}
- {from: demote, to: record}
- {from: conforms, to: test}
- {from: test, to: record}
- {from: record, to: release}
- {from: release, to: end}
# ---------------------------------------------------------------------- D1
- id: D1
family: Delete
name: Deprecate a term
scope: class | relation | attribute | annotation | individual
trigger: A term is superseded or no longer needed.
inputs: [the term, its replacement if any]
outputs: [a deprecated stub, a published version]
note: >
The count at step two is informative, not a condition: a term may be
deprecated whether or not it is instantiated. It becomes a condition only
in D2.
flow:
- {id: start, type: start, name: Term superseded}
- {id: replacement, type: userTask, name: Identify the replacement, or record that there is none}
- {id: count, type: scriptTask, name: Count the instances for information, rules: [EV-015]}
- {id: mark, type: scriptTask, name: Mark deprecated and declare the replacement, rules: [EV-001]}
- {id: strip, type: scriptTask, name: Strip the stub of every edge, rules: [EV-001]}
- {id: label, type: scriptTask, name: Remove any mention of state from the label, rules: [TN-020]}
- {id: release, type: callActivity, calls: X1, name: Propagate and publish}
- {id: end, type: end, name: Term deprecated}
flows:
- {from: start, to: replacement}
- {from: replacement, to: count}
- {from: count, to: mark}
- {from: mark, to: strip}
- {from: strip, to: label}
- {from: label, to: release}
- {from: release, to: end}
# ---------------------------------------------------------------------- D2
- id: D2
family: Delete
name: Withdraw a term permanently
scope: class | relation | attribute | annotation | individual
trigger: A deprecated term is to be removed from the vocabulary.
inputs: [the deprecated term]
outputs: [a vocabulary without the term, a proof of non-instantiation, a published version]
note: >
The reference cleaning step is what prevents phantom nodes: a subject
removed while its identifier is still cited elsewhere is reconstructed by
inference, present in traversals and absent from every control. It is a step
of the procedure, not a check at the end of a script.
flow:
- {id: start, type: start, name: Withdrawal requested}
- {id: count, type: scriptTask, name: Run the counting query, rules: [EV-002, EV-015]}
- {id: instantiated, type: gateway, name: "Any instance found?"}
- {id: keep, type: end, name: Kept deprecated}
- {id: proof, type: scriptTask, name: Attach the proof to the commit, rules: [EV-002, EV-015]}
- {id: remove, type: scriptTask, name: Remove the subject block}
- {id: refs, type: scriptTask, name: Remove every reference naming the subject, rules: [EV-003]}
- {id: check_voc, type: callActivity, calls: R1, name: Verify the vocabulary}
- {id: check_abox, type: callActivity, calls: R2, name: Validate the instances}
- {id: release, type: callActivity, calls: X1, name: Propagate and publish}
- {id: end, type: end, name: Term withdrawn}
flows:
- {from: start, to: count}
- {from: count, to: instantiated}
- {from: instantiated, to: keep, condition: "yes"}
- {from: instantiated, to: proof, condition: "no"}
- {from: proof, to: remove}
- {from: remove, to: refs}
- {from: refs, to: check_voc}
- {from: check_voc, to: check_abox}
- {from: check_abox, to: release}
- {from: release, to: end}
# ---------------------------------------------------------------------- R1
- id: R1
family: Verify
name: Verify the vocabulary against the rulebook
scope: The whole vocabulary, or the subset touched by a change.
trigger: A term has been created, changed or withdrawn, or a release is prepared.
inputs: [the vocabulary, the rulebook source]
outputs: [a conformance report]
note: >
Two tiers run in sequence, not in parallel: the script settles everything
mechanisable, and a person answers only for the rules no script can judge.
Reversing the order wastes review time on findings the script would have
caught.
flow:
- {id: start, type: start, name: Verification requested}
- {id: script, type: scriptTask, name: "Run the naming and declaration checks", rules: [TN-001, TN-002, TN-003, TN-004, TN-005, TN-006, TN-007, TN-009, TN-010, TN-011, TN-012, TN-013, TN-014, TN-015, TN-017, TN-018, TN-019, TN-020, TN-021, TN-023, TN-025, TN-026, TN-027, TN-028, EV-014]}
- {id: mechanised, type: gateway, name: "Any mechanised violation?"}
- {id: report_fail, type: end, name: Report returned with violations}
- {id: human, type: userTask, name: "Review the rules no script can judge", rules: [TN-008, TN-016, TN-022, TN-024]}
- {id: judged, type: gateway, name: "Reviewer raises an issue?"}
- {id: report_ok, type: end, name: Vocabulary conforms}
flows:
- {from: start, to: script}
- {from: script, to: mechanised}
- {from: mechanised, to: report_fail, condition: "at least one"}
- {from: mechanised, to: human, condition: none}
- {from: human, to: judged}
- {from: judged, to: report_fail, condition: "yes"}
- {from: judged, to: report_ok, condition: "no"}
# ---------------------------------------------------------------------- R2
- id: R2
family: Verify
name: Validate instances against the shapes
scope: Any instance graph in the validation perimeter.
trigger: Instances have changed, shapes have changed, or a release is prepared.
inputs: [the instance graph, the shapes, the ontology]
outputs: [a validation report]
note: >
The version check comes first and aborts rather than warns. Validating
against shapes that target another version of the ontology does not fail
loudly: it returns a long list of violations that reads exactly like a
regression of the model.
flow:
- {id: start, type: start, name: Validation requested}
- {id: conforms, type: scriptTask, name: "Compare the declared target version with the ontology version", rules: [EV-010]}
- {id: match, type: gateway, name: "Versions match?"}
- {id: abort, type: end, name: Aborted on version mismatch}
- {id: run, type: scriptTask, name: Run the shape validation}
- {id: violations, type: gateway, name: "Any violation?"}
- {id: fail, type: end, name: Report returned with violations}
- {id: ok, type: end, name: Instances conform}
flows:
- {from: start, to: conforms}
- {from: conforms, to: match}
- {from: match, to: abort, condition: "no"}
- {from: match, to: run, condition: "yes"}
- {from: run, to: violations}
- {from: violations, to: fail, condition: "at least one"}
- {from: violations, to: ok, condition: none}
# ---------------------------------------------------------------------- R3
- id: R3
family: Verify
name: Audit what the shapes cannot see
scope: The whole vocabulary and its source file.
trigger: Periodic audit, or before a version is published.
inputs: [the vocabulary, its source file]
outputs: [a shortlist of suspected duplicates, a list of duplicated blocks]
note: >
Shape validation reads a graph, not a file and not meaning. Two identifiers
standing for the same notion produce two individually valid graphs; a
duplicated block of text produces identical triples and no complaint. This
procedure is the tier those rules fall to, and its output is a shortlist for
a person rather than a verdict.
flow:
- {id: start, type: start, name: Audit requested}
- {id: index, type: scriptTask, name: "Build the normalised label index", rules: [EV-012]}
- {id: hash, type: scriptTask, name: "Hash every normalised subject block", rules: [EV-012]}
- {id: shortlist, type: gateway, name: "Any candidate found?"}
- {id: clean, type: end, name: Nothing to arbitrate}
- {id: review, type: userTask, name: Arbitrate each candidate}
- {id: act, type: gateway, name: "Duplication confirmed?"}
- {id: merge, type: end, name: Referred to the merge procedure}
- {id: dismissed, type: end, name: Candidates dismissed}
flows:
- {from: start, to: index}
- {from: index, to: hash}
- {from: hash, to: shortlist}
- {from: shortlist, to: clean, condition: none}
- {from: shortlist, to: review, condition: "at least one"}
- {from: review, to: act}
- {from: act, to: merge, condition: "yes"}
- {from: act, to: dismissed, condition: "no"}