CUST-WP-0060 T01-T03: canon ratified active, terminology v0.2, schemas + validator
- work-record-types_v0.1.md + autonomy-lanes_v0.1.md: status active (founder ratification 2026-07-20, hub decision f4640f9e); registry grandfathers legacy task-id variants (-T1, -LEGACY-Txx) - workplan-terminology-fleet: v0.2 addendum — work-record umbrella, kind vocabulary, suggestion as legacy bridge name (T02) - canon/standards/schemas/work-records/: spine + intake/decision/ engagement JSON schemas (T03); conditional spine requirements, historical grace for terminal records - tools/validate_work_records.py: registry+schema validator for CI (T04); jsonschema with inlined spine refs, fallback checks without; proof runs clean: the-custodian 384 records, binky-control 45 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
5ccf5fcd90
commit
f4101cdd9a
9 changed files with 378 additions and 7 deletions
|
|
@ -3,7 +3,7 @@ id: canon-autonomy-lanes
|
|||
type: standard
|
||||
title: "Autonomy Lanes (Fleet) v0.1"
|
||||
domain: custodian
|
||||
status: draft
|
||||
status: active
|
||||
version: "0.1"
|
||||
created: "2026-07-20"
|
||||
updated: "2026-07-20"
|
||||
|
|
@ -16,7 +16,7 @@ origin: binky-control/AutonomyPolicy.md (ratified for Binky 2026-07-16, DEC-2026
|
|||
|
||||
# Autonomy Lanes (Fleet) v0.1
|
||||
|
||||
> Draft pending founder ratification (CUST-WP-0060-T01). Promotes the
|
||||
> Ratified by founder 2026-07-20 (CUST-WP-0060-T01, hub decision f4640f9e). Promotes the
|
||||
> binky-control AutonomyPolicy lane model to fleet canon, unchanged in
|
||||
> substance. binky-control's `AutonomyPolicy.md` remains the company-level
|
||||
> policy instance; this standard makes the lane vocabulary and enforcement
|
||||
|
|
|
|||
43
canon/standards/schemas/work-records/decision.schema.json
Normal file
43
canon/standards/schemas/work-records/decision.schema.json
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://canon.coulomb.social/schemas/work-records/decision.schema.json",
|
||||
"title": "Decision",
|
||||
"description": "Authored YAML block for kind=decision (approval package + resolution). Id: {PREFIX}-DEC-YYYY-NNN; legacy DEC-YYYY-NNN grandfathered. Lifecycle: prepared -> resolved(approved|rejected|revised|deferred). 'deferred' is also accepted as a stored status (legacy binky template).",
|
||||
"type": "object",
|
||||
"required": ["id", "title", "status", "lane"],
|
||||
"properties": {
|
||||
"id": { "type": "string", "pattern": "^([A-Z]+-)?DEC-[0-9]{4}-[0-9]{3}$" },
|
||||
"kind": { "const": "decision" },
|
||||
"title": { "type": "string", "minLength": 5 },
|
||||
"status": { "enum": ["prepared", "resolved", "deferred"] },
|
||||
"outcome": {
|
||||
"type": "string",
|
||||
"description": "Free text beginning with approved|rejected|revised|deferred; qualifiers allowed (e.g. 'approved with edits')."
|
||||
},
|
||||
"lane": { "$ref": "spine.schema.json#/$defs/lane" },
|
||||
"owner": { "$ref": "spine.schema.json#/$defs/owner" },
|
||||
"tags": { "$ref": "spine.schema.json#/$defs/tags" },
|
||||
"created_at": { "$ref": "spine.schema.json#/$defs/date" },
|
||||
"needed_by": { "type": "string" },
|
||||
"attention_cost": { "type": "string" },
|
||||
"agent_recommendation": { "type": "string" },
|
||||
"evidence": { "type": "array" },
|
||||
"options": { "type": "array" },
|
||||
"fallback_if_no_response": { "type": "string" },
|
||||
"resolved_at": { "$ref": "spine.schema.json#/$defs/date" },
|
||||
"resolved_by": { "type": "string" },
|
||||
"consequence": { "type": "string" },
|
||||
"state_hub_decision_id": { "$ref": "spine.schema.json#/$defs/uuid" }
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": { "properties": { "status": { "const": "resolved" } }, "required": ["status"] },
|
||||
"then": { "required": ["outcome", "resolved_at", "resolved_by"] }
|
||||
},
|
||||
{
|
||||
"if": { "properties": { "status": { "const": "prepared" } }, "required": ["status"] },
|
||||
"then": { "required": ["agent_recommendation", "fallback_if_no_response"] }
|
||||
}
|
||||
],
|
||||
"additionalProperties": true
|
||||
}
|
||||
29
canon/standards/schemas/work-records/engagement.schema.json
Normal file
29
canon/standards/schemas/work-records/engagement.schema.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://canon.coulomb.social/schemas/work-records/engagement.schema.json",
|
||||
"title": "Engagement",
|
||||
"description": "Authored YAML block for kind=engagement (scheduled external interaction). Id: {PREFIX}-ENG-YYYY-NNN; legacy OH-YYYY-NNN grandfathered. Lifecycle: queued -> prepared -> done.",
|
||||
"type": "object",
|
||||
"required": ["id", "title", "status"],
|
||||
"properties": {
|
||||
"id": { "type": "string", "pattern": "^([A-Z]+-ENG-[0-9]{4}-[0-9]{3}|OH-[0-9]{4}-[0-9]{3})$" },
|
||||
"kind": { "const": "engagement" },
|
||||
"title": { "type": "string", "minLength": 5 },
|
||||
"status": { "enum": ["queued", "prepared", "done"] },
|
||||
"counterparty": { "type": "string" },
|
||||
"lane": { "$ref": "spine.schema.json#/$defs/lane", "description": "Defaults to orange for engagements; explicit value wins." },
|
||||
"owner": { "$ref": "spine.schema.json#/$defs/owner" },
|
||||
"tags": { "$ref": "spine.schema.json#/$defs/tags" },
|
||||
"prepared_material": { "type": "array" },
|
||||
"deadline_pressure": { "type": "string" },
|
||||
"origin": { "type": "string" },
|
||||
"state_hub_engagement_id": { "$ref": "spine.schema.json#/$defs/uuid" }
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": { "properties": { "status": { "const": "prepared" } }, "required": ["status"] },
|
||||
"then": { "required": ["prepared_material", "counterparty"] }
|
||||
}
|
||||
],
|
||||
"additionalProperties": true
|
||||
}
|
||||
44
canon/standards/schemas/work-records/intake.schema.json
Normal file
44
canon/standards/schemas/work-records/intake.schema.json
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://canon.coulomb.social/schemas/work-records/intake.schema.json",
|
||||
"title": "Intake Item",
|
||||
"description": "Authored YAML block for kind=intake. Id: {PREFIX}-IN-NNNN; legacy AWQ-NNN grandfathered. Lifecycle: open -> vetted -> routed -> closed(promoted|declined|absorbed).",
|
||||
"type": "object",
|
||||
"required": ["id", "title"],
|
||||
"properties": {
|
||||
"id": { "type": "string", "pattern": "^([A-Z]+-IN-[0-9]{4}|AWQ-[0-9]{3})$" },
|
||||
"kind": { "const": "intake" },
|
||||
"title": { "type": "string", "minLength": 5 },
|
||||
"status": { "enum": ["open", "vetted", "routed", "closed"] },
|
||||
"outcome": { "enum": ["promoted", "declined", "absorbed"] },
|
||||
"lane": { "$ref": "spine.schema.json#/$defs/lane" },
|
||||
"owner": { "$ref": "spine.schema.json#/$defs/owner" },
|
||||
"tags": { "$ref": "spine.schema.json#/$defs/tags" },
|
||||
"priority": { "enum": ["high", "medium", "low"] },
|
||||
"pulls_from": { "type": "array" },
|
||||
"promoted_to": { "type": "string", "description": "Canonical id of the record this intake was promoted to." },
|
||||
"origin": { "type": "string" },
|
||||
"notes": { "type": "string" },
|
||||
"state_hub_intake_id": { "$ref": "spine.schema.json#/$defs/uuid" }
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": { "properties": { "status": { "const": "closed" } }, "required": ["status"] },
|
||||
"then": { "required": ["outcome"] }
|
||||
},
|
||||
{
|
||||
"if": { "properties": { "outcome": { "const": "promoted" } }, "required": ["outcome"] },
|
||||
"then": { "required": ["promoted_to"] }
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"not": {
|
||||
"properties": { "status": { "const": "closed" } },
|
||||
"required": ["status"]
|
||||
}
|
||||
},
|
||||
"then": { "required": ["lane"] }
|
||||
}
|
||||
],
|
||||
"additionalProperties": true
|
||||
}
|
||||
39
canon/standards/schemas/work-records/spine.schema.json
Normal file
39
canon/standards/schemas/work-records/spine.schema.json
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://canon.coulomb.social/schemas/work-records/spine.schema.json",
|
||||
"title": "Work Record Spine (shared definitions)",
|
||||
"description": "Shared spine per canon/standards/work-record-types_v0.1.md. Kind schemas $ref these definitions. additionalProperties stays open: kind-specific fields extend the spine, never replace it.",
|
||||
"$defs": {
|
||||
"lane": {
|
||||
"enum": ["green", "blue", "yellow", "orange", "red"]
|
||||
},
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
||||
},
|
||||
"date": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": { "type": "string", "pattern": "^[a-z0-9-]+$" }
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"minLength": 2
|
||||
},
|
||||
"spineOpen": {
|
||||
"type": "object",
|
||||
"description": "Spine requirements for records in a non-terminal status.",
|
||||
"required": ["id", "lane"],
|
||||
"properties": {
|
||||
"lane": { "$ref": "#/$defs/lane" },
|
||||
"owner": { "$ref": "#/$defs/owner" },
|
||||
"tags": { "$ref": "#/$defs/tags" },
|
||||
"uuid": { "$ref": "#/$defs/uuid" },
|
||||
"created_at": { "$ref": "#/$defs/date" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
# CI and fix-consistency validate id patterns against this file.
|
||||
# New kinds/prefixes land here by canon PR only.
|
||||
version: "0.1"
|
||||
status: draft
|
||||
status: active
|
||||
kinds:
|
||||
- kind: workplan
|
||||
id_patterns:
|
||||
|
|
@ -13,6 +13,13 @@ kinds:
|
|||
- "^[A-Z]+-WP-[0-9]{4}-T[0-9]{2,3}$"
|
||||
- "^ADHOC-[0-9]{4}-[0-9]{2}-[0-9]{2}-T[0-9]{2}$"
|
||||
lifecycle: [wait, todo, progress, done, cancel]
|
||||
legacy_patterns:
|
||||
- pattern: "^[A-Z]+-WP-[0-9]{4}-T[0-9]$"
|
||||
source: early single-digit task suffixes (pre task-block convention)
|
||||
grandfathered: true
|
||||
- pattern: "^[A-Z]+-WP-[0-9]{4}-LEGACY-T[0-9]{2}$"
|
||||
source: archived-workplan legacy task renumbering
|
||||
grandfathered: true
|
||||
- kind: intake
|
||||
id_patterns:
|
||||
- "^[A-Z]+-IN-[0-9]{4}$"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ id: canon-work-record-types
|
|||
type: standard
|
||||
title: "Work Record Types & Identity (Fleet) v0.1"
|
||||
domain: custodian
|
||||
status: draft
|
||||
status: active
|
||||
version: "0.1"
|
||||
created: "2026-07-20"
|
||||
updated: "2026-07-20"
|
||||
|
|
@ -16,7 +16,7 @@ machine_copy: canon/standards/work-record-types.yaml
|
|||
|
||||
# Work Record Types & Identity (Fleet) v0.1
|
||||
|
||||
> Draft pending founder ratification (CUST-WP-0060-T01). Source:
|
||||
> Ratified by founder 2026-07-20 (CUST-WP-0060-T01, hub decision f4640f9e). Source:
|
||||
> `research/WorkOrchestrationArchitectureDraft.md` v0.2 (founder-reviewed
|
||||
> 2026-07-20). Extends — does not replace —
|
||||
> `workplan-terminology-fleet_v0.1.md` and ADR-001/ADR-005.
|
||||
|
|
|
|||
|
|
@ -4,14 +4,15 @@ type: standard
|
|||
title: "Workplan Terminology (Fleet) v0.1"
|
||||
domain: custodian
|
||||
status: active
|
||||
version: "0.1"
|
||||
version: "0.2"
|
||||
created: "2026-07-08"
|
||||
updated: "2026-07-08"
|
||||
updated: "2026-07-20"
|
||||
scope: fleet
|
||||
related_workplans:
|
||||
- CUST-WP-0055
|
||||
- STATE-WP-0054
|
||||
- STATE-WP-0069
|
||||
- CUST-WP-0060
|
||||
supersedes: none
|
||||
---
|
||||
|
||||
|
|
@ -43,6 +44,21 @@ Authoritative interface matrix for State Hub:
|
|||
| Hub index row | **workplan** (product term) | DB entity keyed by UUID; may still use legacy column names internally |
|
||||
| Task within a workplan | **task** | `id: {WP}-TNN` in workplan file blocks |
|
||||
|
||||
## Work-record umbrella (v0.2 addendum, CUST-WP-0060)
|
||||
|
||||
The workplan and task definitions above are unchanged. Since v0.2 they are
|
||||
two *kinds* of the fleet umbrella term **work record** — any identified,
|
||||
lifecycle-bearing coordination artefact. The full kind registry (workplan,
|
||||
task, **intake item**, **decision**, **engagement**, **register entry**),
|
||||
the conventional spine, identity layering (UUIDv7 internal, canonical names
|
||||
external), and grandfathered legacy id schemes (`AWQ-`, `DEC-`, `OH-`) are
|
||||
normative in `work-record-types_v0.1.md` (+ machine registry
|
||||
`work-record-types.yaml`). Autonomy-lane vocabulary is normative in
|
||||
`autonomy-lanes_v0.1.md`. "Suggestion" is a legacy bridge name for the
|
||||
intake kind (state-hub suggestions table: read-only legacy pending
|
||||
close-out). This file keeps its `_v0.1` name for reference stability; the
|
||||
frontmatter `version` field is authoritative.
|
||||
|
||||
Use **workplan** in human-facing prose: `SCOPE.md`, `AGENTS.md`, `INTENT.md`,
|
||||
README, workplan bodies, operator runbooks, and agent session guidance.
|
||||
|
||||
|
|
|
|||
193
tools/validate_work_records.py
Normal file
193
tools/validate_work_records.py
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Validate work-record YAML blocks against the canon kind registry.
|
||||
|
||||
Canon: canon/standards/work-record-types_v0.1.md (+ work-record-types.yaml)
|
||||
Schemas: canon/standards/schemas/work-records/*.schema.json
|
||||
|
||||
Scans Markdown files for fenced ```yaml and ```task blocks. A block is a
|
||||
work record iff it parses to a mapping whose `id` matches a registered (or
|
||||
grandfathered legacy) id pattern. Records are validated against their
|
||||
kind's JSON schema; ``task`` blocks get pattern + lifecycle checks (they
|
||||
are already parsed authoritatively by state-hub).
|
||||
|
||||
Blocks with an id-like string that matches *no* registered pattern produce
|
||||
a warning (CI-level sidetrack hint; the authoritative detector is
|
||||
fix-consistency C-25).
|
||||
|
||||
Usage:
|
||||
validate_work_records.py [--repo PATH] [--canon PATH] [--strict]
|
||||
|
||||
--strict turns sidetrack warnings into errors. Exit 1 on any error.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
FENCE_RE = re.compile(r"```(yaml|task)\n(.*?)```", re.S)
|
||||
ID_LIKE_RE = re.compile(r"^[A-Z][A-Z0-9]*(-[A-Z0-9]+)+-?[0-9]*$")
|
||||
SKIP_DIRS = {".git", "node_modules", ".venv", "history", "agents_backup"}
|
||||
TERMINAL = {"closed", "resolved", "done", "cancel", "finished", "archived"}
|
||||
|
||||
# task blocks are validated inline (state-hub owns their full parsing)
|
||||
TASK_ID_RE = re.compile(r"^([A-Z]+-WP-[0-9]{4}-(LEGACY-)?T[0-9]{1,3}"
|
||||
r"|ADHOC-[0-9]{4}-[0-9]{2}-[0-9]{2}-T[0-9]{2})$")
|
||||
TASK_STATUS = {"wait", "todo", "progress", "done", "cancel",
|
||||
"in_progress", "blocked"} # aliases per migration window
|
||||
|
||||
|
||||
def load_registry(canon: Path) -> list[dict]:
|
||||
reg = yaml.safe_load(
|
||||
(canon / "canon/standards/work-record-types.yaml").read_text())
|
||||
kinds = []
|
||||
for k in reg["kinds"]:
|
||||
pats = [re.compile(p) for p in k.get("id_patterns", [])]
|
||||
pats += [re.compile(lp["pattern"])
|
||||
for lp in k.get("legacy_patterns", [])]
|
||||
kinds.append({"kind": k["kind"], "patterns": pats})
|
||||
return kinds
|
||||
|
||||
|
||||
def load_validators(canon: Path):
|
||||
"""Return {kind: callable(block) -> list[str]} using jsonschema if
|
||||
available, else minimal fallback checks."""
|
||||
schema_dir = canon / "canon/standards/schemas/work-records"
|
||||
schemas = {p.stem.replace(".schema", ""): json.loads(p.read_text())
|
||||
for p in schema_dir.glob("*.schema.json")}
|
||||
try:
|
||||
import jsonschema
|
||||
|
||||
spine_defs = schemas["spine"]["$defs"]
|
||||
|
||||
def inline(node):
|
||||
"""Replace spine.schema.json#/$defs/X refs with the def body
|
||||
so no ref registry is needed (works on any jsonschema>=4)."""
|
||||
if isinstance(node, dict):
|
||||
ref = node.get("$ref", "")
|
||||
if ref.startswith("spine.schema.json#/$defs/"):
|
||||
merged = dict(spine_defs[ref.rsplit("/", 1)[1]])
|
||||
merged.update({k: v for k, v in node.items()
|
||||
if k != "$ref"})
|
||||
return inline(merged)
|
||||
return {k: inline(v) for k, v in node.items()}
|
||||
if isinstance(node, list):
|
||||
return [inline(v) for v in node]
|
||||
return node
|
||||
|
||||
def make(kind):
|
||||
validator = jsonschema.Draft202012Validator(
|
||||
inline(schemas[kind]))
|
||||
|
||||
def check(block):
|
||||
return [f"{e.json_path}: {e.message}"
|
||||
for e in validator.iter_errors(block)]
|
||||
return check
|
||||
|
||||
return {k: make(k) for k in schemas if k != "spine"}
|
||||
except ImportError:
|
||||
def fallback(kind):
|
||||
def check(block):
|
||||
errs = []
|
||||
if not block.get("title") and kind != "task":
|
||||
errs.append("missing title (fallback check)")
|
||||
if block.get("status") not in TERMINAL and \
|
||||
"lane" not in block and kind == "intake":
|
||||
errs.append("open record missing lane (fallback check)")
|
||||
return errs
|
||||
return check
|
||||
print("note: jsonschema not installed — minimal fallback checks only",
|
||||
file=sys.stderr)
|
||||
return {k: fallback(k) for k in schemas if k != "spine"}
|
||||
|
||||
|
||||
def classify(record_id: str, kinds: list[dict]) -> str | None:
|
||||
for k in kinds:
|
||||
if any(p.match(record_id) for p in k["patterns"]):
|
||||
return k["kind"]
|
||||
return None
|
||||
|
||||
|
||||
def iter_blocks(md: Path, kinds: list[dict]):
|
||||
for fence, body in FENCE_RE.findall(md.read_text(errors="replace")):
|
||||
try:
|
||||
docs = list(yaml.safe_load_all(body))
|
||||
except yaml.YAMLError as exc:
|
||||
# only an error if the raw text plausibly holds a registered id
|
||||
raw_ids = re.findall(r"^id:\s*(\S+)", body, re.M)
|
||||
if any(classify(r.strip("\"'"), kinds) for r in raw_ids):
|
||||
yield fence, None, f"unparseable YAML block: {exc}"
|
||||
continue
|
||||
for data in docs:
|
||||
if isinstance(data, dict):
|
||||
yield fence, data, None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--repo", default=".", type=Path)
|
||||
ap.add_argument("--canon", default=None, type=Path,
|
||||
help="the-custodian checkout (default: script's repo)")
|
||||
ap.add_argument("--strict", action="store_true")
|
||||
args = ap.parse_args()
|
||||
canon = args.canon or Path(__file__).resolve().parents[1]
|
||||
|
||||
kinds = load_registry(canon)
|
||||
validators = load_validators(canon)
|
||||
errors, warnings, checked = [], [], 0
|
||||
|
||||
md_files = [p for p in sorted(args.repo.rglob("*.md"))
|
||||
if not any(part in SKIP_DIRS for part in p.parts)]
|
||||
for md in md_files:
|
||||
rel = md.relative_to(args.repo)
|
||||
for fence, block, parse_err in iter_blocks(md, kinds):
|
||||
if parse_err:
|
||||
errors.append(f"{rel}: {parse_err}")
|
||||
continue
|
||||
rid = block.get("id")
|
||||
if not isinstance(rid, str):
|
||||
continue
|
||||
if "NNN" in rid or rid.endswith("-TNN"):
|
||||
continue # template placeholder, not a record
|
||||
kind = classify(rid, kinds)
|
||||
if kind is None:
|
||||
if ID_LIKE_RE.match(rid):
|
||||
warnings.append(
|
||||
f"{rel}: '{rid}' looks like a work-record id but "
|
||||
f"matches no registered pattern — unregistered "
|
||||
f"species are sidetracks (work-record-types_v0.1)")
|
||||
continue
|
||||
checked += 1
|
||||
if fence == "task" or kind == "task":
|
||||
if not TASK_ID_RE.match(rid):
|
||||
errors.append(f"{rel}: task id '{rid}' malformed")
|
||||
st = block.get("status")
|
||||
if st is not None and st not in TASK_STATUS:
|
||||
errors.append(f"{rel}: {rid}: bad task status '{st}'")
|
||||
continue
|
||||
if kind in validators:
|
||||
for msg in validators[kind](block):
|
||||
# historical grace: terminal records only get id/status
|
||||
# structural errors, not spine completeness
|
||||
if block.get("status") in TERMINAL and "required" in msg:
|
||||
continue
|
||||
errors.append(f"{rel}: {rid}: {msg}")
|
||||
|
||||
for w in warnings:
|
||||
print(f"WARN {w}")
|
||||
for e in errors:
|
||||
print(f"ERROR {e}")
|
||||
print(f"work-records: {checked} checked, "
|
||||
f"{len(errors)} errors, {len(warnings)} warnings")
|
||||
if errors or (args.strict and warnings):
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue