This page defines what every command shares: the reader table, the shared input validation, the view filter, the CI run context and the envelope. Developers who change a command read it.
Read before: CLI · Next: normalize-docs
- Modules
- The reader table
- Shared input validation
- The view filter
- The CI run context
- Shared envelope
- Usage
| Module | What it holds |
|---|---|
living_doc_core.logging_config |
setup_logging(verbose): the living_doc logger, [LEVEL] message on stderr |
living_doc_core.envelope |
The toolkit producer identity and forwarded(warning, source), shared by every transform |
living_doc_core.readers |
The reader table, READERS, and the readers read_documentation and read_tests |
living_doc_core.input_validation |
validate_inputs(command, inputs), which runs the shared input validation over one command's inputs |
living_doc_core.views |
select_records(entities, view), the view filter |
living_doc_core.run_context |
run_context(), the CI run a file is written in, for metadata.run |
- It depends on
living-doc-utilities==0.8.0and nothing else; the contract models and schemas are that package's living_doc_coreitself exports onlysetup_loggingand__version__; every other module loads the contract models, so it is imported by name- Why: it keeps the contract-model imports behind the module boundary that needs them.
READERS maps each contract the toolkit reads, by contract name, to its role and the producers known to write it:
| Contract | Role | Known producers |
|---|---|---|
doc-source |
documentation | AbsaOSS/living-doc-collector-gh |
ui-tests |
tests | AbsaOSS/living-doc-collector-gh |
- It is keyed by contract name, never by producer: the file's own
schema_versionsays how it is read - Every read goes through
read_artifact(path, expected=<the role's contract names>), which runs R5, and returns the contract's model:read_documentationaDocSourceResult,read_testsaUITestsResult; neither returns adict - The issue-tracker documentation contract,
doc-entities, joins the documentation role in a later release, through a spec → Reader-table spec
Each command declares the roles it takes, one input each:
| Command | Documentation input | Tests input |
|---|---|---|
normalize-docs |
one | none |
normalize-tests |
none | one |
coverage-matrix |
one | one |
validate_inputs(command, {Role.DOCUMENTATION: [...], Role.TESTS: [...]}) runs these steps in order:
MISSING_INPUT: a role the command takes with no path, or a path that names no file.- R5 per input, through
read_artifact:INVALID_CONTRACT_ID,CONTRACT_MISMATCH,SCHEMA_VALIDATION_FAILED. A file that is not JSON fails here too, with a line that carries no code. UNKNOWN_PRODUCER:metadata.producer.nameis not a known producer of the file's contract.PROJECT_MISMATCH: the inputs name differentmetadata.source.project_ids.MULTIPLE_DOCUMENTATION_SOURCES: more than one documentation file; a run reads exactly one.DUPLICATE_ENTITY_ID,DUPLICATE_AC_ID: an entity id twice in the documentation, or a criterion id twice under one entity, listing every place.
- The first failing step ends validation:
InputValidationErrorcarries every occurrence of that step, and nothing from a later step- Each occurrence is an
InputProblemwith its code, the input it is in and a hint. - Why: a reader never gets a cascade of errors caused by the first one, and no precedence rule picks a winner.
- Each occurrence is an
- An
InputProblemreads as[CODE] <role> input '<path>': <what is wrong>; <hint> - Once every step passes, it returns
ValidatedInputs: the documentation and tests as contract models,Nonefor a role the command does not take metadata.producer.versionis audit information: it is never compared, and an unfamiliar one passes silentlyMIXED_DOCUMENTATION_SOURCESneeds a second documentation contract, so it arrives withdoc-entities→ errors- An unknown command, a role the command does not take, or a second tests path is a
ValueError: a command's own options rule them out - The steps and codes are the errors page's § Shared input validation order; the one-documentation-file rule is the pipeline rules' § One project, one pipeline, one documentation source → pipeline rules
select_records(entities, view) is the one view filter: a transform runs it to pick the records a view holds. It
returns the kept entities and the contract's own SelectionSummary:
innerkeeps every record.releasedropsplannedandin_reviewentities and, of the entities it keeps,plannedandin_reviewacceptance criteria; it keepsdeprecatedones. A Feature is dropped by its derived state- It selects records only and never edits a field: a kept entity is the input's own, or a copy without its dropped criteria
- The acceptance-criteria counts cover kept entities only; a dropped entity's criteria are not counted.
- A view other than
innerorreleaseis aValueError. - The rule is the rendering page's § Records and presentation; how a generator shows each view is
living-doc's guide → document types
run_context() returns the contract's Run, read from the GitHub Actions variables of the run the command runs in:
| Field | Variable |
|---|---|
run_id |
GITHUB_RUN_ID |
run_attempt |
GITHUB_RUN_ATTEMPT |
actor |
GITHUB_ACTOR |
workflow |
GITHUB_WORKFLOW |
ref |
GITHUB_REF |
sha |
GITHUB_SHA |
- An unset or empty variable is
None, so outside GitHub Actions every field isNone - A command sets it as its output's
metadata.run. It is audit information: nothing reads it to decide anything → Shared envelope - An input's own
runstays in itsmetadata.source_inputs[]entry → Shared envelope runis defined on the artifact rules' § The metadata envelope → artifact rules
Every command's output carries these fields the same way; each command's page gives the rest:
| Field | Value |
|---|---|
metadata.producer |
name AbsaOSS/living-doc-toolkit, PRODUCER_NAME; version the toolkit's one version, TOOLKIT_VERSION, read from the installed living-doc-core; build null; write_artifact fills producer.utilities_version |
metadata.run |
the CI run the file is written in (The CI run context); empty outside GitHub Actions |
metadata.generated_at |
the write time, in UTC, to the second |
metadata.stats |
computed by write_artifact from the output's own records; write_artifact validates the output before it writes |
metadata.producer.buildstaysnull: the canon defines it as the writing tool's own build; the CI run ismetadata.run→ artifact rules- Each input's own
runandstatsstay in itsmetadata.source_inputs[]entry → artifact rules - The output's
sources_configured,sources_failed,unresolved_refsandentities_skippedare0: a transform configures no source - The canon's rules are the artifact rules' § The metadata envelope, R6 and R7 → artifact rules
forwarded(warning, source) copies an input warning unchanged but for context:
contextnames the input first,source_input='<schema_version>', the input's contract id, then keeps the warning's own context after a space:source_input='doc-source-v1.0.0' entity_id='FEAT-003' target='FEAT-002'.- Why the contract id: it is the
schema_versionof the input's ownmetadata.source_inputs[]entry, so the name points into the same file. - A run reads one input per contract, and a file name or path would depend on where the run happened.
- Why
source_input=: the key followsmetadata.source_inputs[]. A collector's own context can already holdinput=, naming its configuration input. - Such a context, e.g.
input='doc-source-repositories' entry=1 repository='org/repo', stays as it is after the forwarded name.
- Why the contract id: it is the
- A warning without a context gets only
source_input='<schema_version>'. code,message,entity_id,ac_id,line_no,pathandsource_urlare not touched.- The rule is the errors page's § Forwarded warnings → errors
from living_doc_core import setup_logging
from living_doc_core.input_validation import InputValidationError, validate_inputs
from living_doc_core.readers import Role
logger = setup_logging(verbose=False)
try:
inputs = validate_inputs("coverage-matrix", {Role.DOCUMENTATION: ["doc-source.json"], Role.TESTS: ["ui-tests.json"]})
except InputValidationError as error:
for problem in error.problems:
logger.error("%s", problem)