# 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/.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"}