codex-workflow-v2 2.0.0-beta.1 → 2.0.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +94 -12
  2. package/dist/src/alpha6/adoption.js +13 -3
  3. package/dist/src/alpha6/adoption.js.map +1 -1
  4. package/dist/src/alpha6/handoff.d.ts +26 -2
  5. package/dist/src/alpha6/handoff.js +277 -29
  6. package/dist/src/alpha6/handoff.js.map +1 -1
  7. package/dist/src/alpha6/mechanical-feasibility.d.ts +7 -0
  8. package/dist/src/alpha6/mechanical-feasibility.js +303 -0
  9. package/dist/src/alpha6/mechanical-feasibility.js.map +1 -0
  10. package/dist/src/alpha6/milestone.d.ts +37 -2
  11. package/dist/src/alpha6/milestone.js +472 -33
  12. package/dist/src/alpha6/milestone.js.map +1 -1
  13. package/dist/src/alpha6/plan-integrity.d.ts +14 -0
  14. package/dist/src/alpha6/plan-integrity.js +127 -0
  15. package/dist/src/alpha6/plan-integrity.js.map +1 -0
  16. package/dist/src/alpha6/plan-risk.d.ts +1 -0
  17. package/dist/src/alpha6/plan-risk.js +15 -7
  18. package/dist/src/alpha6/plan-risk.js.map +1 -1
  19. package/dist/src/alpha6/remediation.d.ts +2 -1
  20. package/dist/src/alpha6/remediation.js +186 -9
  21. package/dist/src/alpha6/remediation.js.map +1 -1
  22. package/dist/src/alpha6/review.d.ts +3 -0
  23. package/dist/src/alpha6/review.js +24 -0
  24. package/dist/src/alpha6/review.js.map +1 -1
  25. package/dist/src/cli.js +486 -46
  26. package/dist/src/cli.js.map +1 -1
  27. package/dist/src/contracts.d.ts +238 -6
  28. package/dist/src/contracts.js +1 -1
  29. package/dist/src/credential-output.d.ts +10 -0
  30. package/dist/src/credential-output.js +73 -0
  31. package/dist/src/credential-output.js.map +1 -0
  32. package/dist/src/dependency-provenance.d.ts +19 -0
  33. package/dist/src/dependency-provenance.js +185 -0
  34. package/dist/src/dependency-provenance.js.map +1 -0
  35. package/dist/src/errors.d.ts +1 -1
  36. package/dist/src/errors.js.map +1 -1
  37. package/dist/src/git.d.ts +1 -1
  38. package/dist/src/git.js +13 -5
  39. package/dist/src/git.js.map +1 -1
  40. package/dist/src/index.d.ts +2 -1
  41. package/dist/src/index.js +2 -1
  42. package/dist/src/index.js.map +1 -1
  43. package/dist/src/lifecycle/canonical-hash.d.ts +6 -0
  44. package/dist/src/lifecycle/canonical-hash.js +24 -0
  45. package/dist/src/lifecycle/canonical-hash.js.map +1 -0
  46. package/dist/src/lifecycle/catalog.d.ts +26 -0
  47. package/dist/src/lifecycle/catalog.js +208 -0
  48. package/dist/src/lifecycle/catalog.js.map +1 -0
  49. package/dist/src/lifecycle/compiler-inspection.d.ts +48 -0
  50. package/dist/src/lifecycle/compiler-inspection.js +29 -0
  51. package/dist/src/lifecycle/compiler-inspection.js.map +1 -0
  52. package/dist/src/lifecycle/core-static-readiness.d.ts +6 -0
  53. package/dist/src/lifecycle/core-static-readiness.js +130 -0
  54. package/dist/src/lifecycle/core-static-readiness.js.map +1 -0
  55. package/dist/src/lifecycle/corrective-replan-authority.d.ts +53 -0
  56. package/dist/src/lifecycle/corrective-replan-authority.js +139 -0
  57. package/dist/src/lifecycle/corrective-replan-authority.js.map +1 -0
  58. package/dist/src/lifecycle/corrective-replan-binding-manifest.d.ts +21 -0
  59. package/dist/src/lifecycle/corrective-replan-binding-manifest.js +35 -0
  60. package/dist/src/lifecycle/corrective-replan-binding-manifest.js.map +1 -0
  61. package/dist/src/lifecycle/corrective-replan-credential-core.d.ts +86 -0
  62. package/dist/src/lifecycle/corrective-replan-credential-core.js +563 -0
  63. package/dist/src/lifecycle/corrective-replan-credential-core.js.map +1 -0
  64. package/dist/src/lifecycle/corrective-replan-credential-schema.d.ts +17 -0
  65. package/dist/src/lifecycle/corrective-replan-credential-schema.js +44 -0
  66. package/dist/src/lifecycle/corrective-replan-credential-schema.js.map +1 -0
  67. package/dist/src/lifecycle/corrective-replan-credentials.d.ts +47 -0
  68. package/dist/src/lifecycle/corrective-replan-credentials.js +103 -0
  69. package/dist/src/lifecycle/corrective-replan-credentials.js.map +1 -0
  70. package/dist/src/lifecycle/corrective-replan.d.ts +64 -0
  71. package/dist/src/lifecycle/corrective-replan.js +553 -0
  72. package/dist/src/lifecycle/corrective-replan.js.map +1 -0
  73. package/dist/src/lifecycle/evaluator.d.ts +2 -0
  74. package/dist/src/lifecycle/evaluator.js +35 -0
  75. package/dist/src/lifecycle/evaluator.js.map +1 -0
  76. package/dist/src/lifecycle/fingerprint.d.ts +31 -0
  77. package/dist/src/lifecycle/fingerprint.js +388 -0
  78. package/dist/src/lifecycle/fingerprint.js.map +1 -0
  79. package/dist/src/lifecycle/immutable.d.ts +1 -0
  80. package/dist/src/lifecycle/immutable.js +10 -0
  81. package/dist/src/lifecycle/immutable.js.map +1 -0
  82. package/dist/src/lifecycle/implementation-table.d.ts +29 -0
  83. package/dist/src/lifecycle/implementation-table.js +87 -0
  84. package/dist/src/lifecycle/implementation-table.js.map +1 -0
  85. package/dist/src/lifecycle/index.d.ts +4 -0
  86. package/dist/src/lifecycle/index.js +5 -0
  87. package/dist/src/lifecycle/index.js.map +1 -0
  88. package/dist/src/lifecycle/schema-artifact.d.ts +12 -0
  89. package/dist/src/lifecycle/schema-artifact.js +214 -0
  90. package/dist/src/lifecycle/schema-artifact.js.map +1 -0
  91. package/dist/src/lifecycle/semantic-registry.d.ts +6 -0
  92. package/dist/src/lifecycle/semantic-registry.js +489 -0
  93. package/dist/src/lifecycle/semantic-registry.js.map +1 -0
  94. package/dist/src/lifecycle/types.d.ts +718 -0
  95. package/dist/src/lifecycle/types.js +4 -0
  96. package/dist/src/lifecycle/types.js.map +1 -0
  97. package/dist/src/observation.d.ts +20 -0
  98. package/dist/src/observation.js +171 -0
  99. package/dist/src/observation.js.map +1 -0
  100. package/dist/src/reviewer.d.ts +43 -1
  101. package/dist/src/reviewer.js +203 -50
  102. package/dist/src/reviewer.js.map +1 -1
  103. package/dist/src/state/corrective-replan-executor.d.ts +64 -0
  104. package/dist/src/state/corrective-replan-executor.js +608 -0
  105. package/dist/src/state/corrective-replan-executor.js.map +1 -0
  106. package/dist/src/state/corrective-replan-public-schema.d.ts +3 -0
  107. package/dist/src/state/corrective-replan-public-schema.js +43 -0
  108. package/dist/src/state/corrective-replan-public-schema.js.map +1 -0
  109. package/dist/src/state/corrective-replan-public.d.ts +95 -0
  110. package/dist/src/state/corrective-replan-public.js +665 -0
  111. package/dist/src/state/corrective-replan-public.js.map +1 -0
  112. package/dist/src/state/corrective-replan-transaction.d.ts +160 -0
  113. package/dist/src/state/corrective-replan-transaction.js +730 -0
  114. package/dist/src/state/corrective-replan-transaction.js.map +1 -0
  115. package/dist/src/state/corrective-yield-executor.d.ts +31 -0
  116. package/dist/src/state/corrective-yield-executor.js +236 -0
  117. package/dist/src/state/corrective-yield-executor.js.map +1 -0
  118. package/dist/src/state/corrective-yield-transaction.d.ts +97 -0
  119. package/dist/src/state/corrective-yield-transaction.js +591 -0
  120. package/dist/src/state/corrective-yield-transaction.js.map +1 -0
  121. package/dist/src/state/lock.d.ts +76 -3
  122. package/dist/src/state/lock.js +611 -30
  123. package/dist/src/state/lock.js.map +1 -1
  124. package/dist/src/state/store.d.ts +3 -0
  125. package/dist/src/state/store.js +65 -12
  126. package/dist/src/state/store.js.map +1 -1
  127. package/dist/src/transition-core.d.ts +67 -0
  128. package/dist/src/transition-core.js +318 -0
  129. package/dist/src/transition-core.js.map +1 -0
  130. package/dist/src/version.d.ts +1 -1
  131. package/dist/src/version.js +1 -1
  132. package/dist/src/version.js.map +1 -1
  133. package/dist/src/workflow.d.ts +88 -24
  134. package/dist/src/workflow.js +1647 -270
  135. package/dist/src/workflow.js.map +1 -1
  136. package/docs/autonomy-guardrails.md +61 -1
  137. package/docs/beta11-plan-integrity-recovery-brief.md +38 -0
  138. package/docs/beta2-initial-assembly-navigation-brief.md +616 -0
  139. package/docs/delegated-approval.md +26 -1
  140. package/docs/development-flow.md +63 -8
  141. package/docs/lifecycle/state-machine-stabilization.md +641 -0
  142. package/docs/pdf/README.md +24 -0
  143. package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
  144. package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
  145. package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
  146. package/docs/pdf/requirements.txt +1 -0
  147. package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +234 -0
  148. package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +334 -0
  149. package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +413 -0
  150. package/docs/problem-briefs/01-pre-implementation-integrity.md +478 -0
  151. package/docs/problem-briefs/02-minimal-step-integrity.md +411 -0
  152. package/docs/problem-briefs/03-minimal-agent-context-integrity.md +358 -0
  153. package/docs/problem-briefs/04-task-dependency-and-structural-replacement-integrity.md +566 -0
  154. package/docs/problem-briefs/BRIEF-TEMPLATE.md +56 -0
  155. package/docs/problem-briefs/README.md +120 -0
  156. package/docs/problem-briefs/evidence/p01-mechanical-feasibility-corpus.md +90 -0
  157. package/docs/problem-briefs/evidence/signal-v4-pre-m3-replay.md +246 -0
  158. package/docs/release.md +30 -6
  159. package/docs/split-required-recovery.md +19 -25
  160. package/docs/stable-release-defect-register.md +465 -3
  161. package/docs/updating-existing-project.md +59 -4
  162. package/docs/validation-report.md +124 -56
  163. package/package.json +9 -1
  164. package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +10 -3
  165. package/plugins/codex-workflow-gateway/references/protocol.md +92 -4
  166. package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +376 -0
  167. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +263 -4
  168. package/references/state-machine.md +13 -6
  169. package/roles/delivery-coordinator.md +39 -0
  170. package/schemas/adoption-posture-event.schema.json +1 -1
  171. package/schemas/authorization-event.schema.json +64 -1
  172. package/schemas/context-envelope.schema.json +1 -1
  173. package/schemas/corrective-decision-event.schema.json +48 -3
  174. package/schemas/corrective-replan-credentials.private.schema.json +98 -0
  175. package/schemas/corrective-replan-execution-event.private.schema.json +53 -0
  176. package/schemas/corrective-replan-public.schema.json +142 -0
  177. package/schemas/corrective-replan-task-transaction.private.schema.json +151 -0
  178. package/schemas/corrective-yield-task-transaction.private.schema.json +98 -0
  179. package/schemas/milestone-scope-change-event.schema.json +6 -1
  180. package/schemas/milestone.schema.json +6 -1
  181. package/schemas/remediation-event.schema.json +11 -1
  182. package/schemas/task-handoff-event.schema.json +19 -3
  183. package/schemas/task.schema.json +24 -1
  184. package/schemas/transition-definition.schema.json +2609 -0
  185. package/schemas/transition-payloads.schema.json +140 -0
  186. package/scripts/generate-pdf-docs.py +512 -0
  187. package/scripts/run-pdf-docs.mjs +62 -0
@@ -0,0 +1,140 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://codex-workflow.local/schemas/transition-payloads.schema.json",
4
+ "$defs": {
5
+ "taskId": { "type": "string", "minLength": 1 },
6
+ "expectedRevision": { "type": "integer", "minimum": 0 },
7
+ "nonEmptyString": { "type": "string", "minLength": 1 },
8
+ "stringArray": { "type": "array", "items": { "type": "string" } },
9
+ "taskPlanInput": {
10
+ "type": "object",
11
+ "required": ["objective", "requirements", "acceptance", "risks", "knowledgeImpact", "knowledgeImpactReason", "knowledgeTargets", "knowledgeMapRevision", "knowledgeMapHash", "graphUse", "steps"],
12
+ "properties": {
13
+ "objective": { "$ref": "#/$defs/nonEmptyString" },
14
+ "requirements": { "$ref": "#/$defs/stringArray" },
15
+ "acceptance": { "$ref": "#/$defs/stringArray" },
16
+ "risks": { "$ref": "#/$defs/stringArray" },
17
+ "knowledgeImpact": { "enum": ["none", "update", "create", "retire"] },
18
+ "knowledgeImpactReason": { "type": "string" },
19
+ "knowledgeTargets": { "type": "array", "items": { "$ref": "#/$defs/knowledgeTarget" } },
20
+ "knowledgeMapRevision": { "type": "integer", "minimum": 0 },
21
+ "knowledgeMapHash": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
22
+ "graphUse": { "enum": ["code-aware", "not-applicable"] },
23
+ "steps": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/planStep" } }
24
+ },
25
+ "additionalProperties": false
26
+ },
27
+ "knowledgeTarget": {
28
+ "type": "object",
29
+ "required": ["category", "path", "reason", "requirementIds"],
30
+ "properties": {
31
+ "category": { "enum": ["agent-guidance", "onboarding", "architecture", "decisions", "commands", "ownership", "testing", "operations", "domain"] },
32
+ "path": { "$ref": "#/$defs/nonEmptyString" },
33
+ "reason": { "$ref": "#/$defs/nonEmptyString" },
34
+ "requirementIds": { "$ref": "#/$defs/stringArray" }
35
+ },
36
+ "additionalProperties": false
37
+ },
38
+ "planStep": {
39
+ "type": "object",
40
+ "required": ["id", "title", "objective", "requirements", "dependencies", "expectedOutputs", "allowedWrites", "forbiddenScope", "checks"],
41
+ "properties": {
42
+ "id": { "$ref": "#/$defs/nonEmptyString" },
43
+ "title": { "$ref": "#/$defs/nonEmptyString" },
44
+ "objective": { "$ref": "#/$defs/nonEmptyString" },
45
+ "requirements": { "$ref": "#/$defs/stringArray" },
46
+ "dependencies": { "$ref": "#/$defs/stringArray" },
47
+ "expectedOutputs": { "$ref": "#/$defs/stringArray" },
48
+ "allowedWrites": { "$ref": "#/$defs/stringArray" },
49
+ "forbiddenScope": { "$ref": "#/$defs/stringArray" },
50
+ "checks": { "$ref": "#/$defs/stringArray" }
51
+ },
52
+ "additionalProperties": false
53
+ },
54
+ "planRiskAuditInput": {
55
+ "type": "object",
56
+ "required": ["summary", "decision", "splitRationale", "stepClassifications"],
57
+ "properties": {
58
+ "summary": { "$ref": "#/$defs/nonEmptyString" },
59
+ "decision": { "enum": ["approved", "approved-with-rationale", "split-required", "stop-escalate"] },
60
+ "splitRationale": { "type": ["string", "null"] },
61
+ "stepClassifications": { "type": "array", "items": { "$ref": "#/$defs/riskStepClassification" } }
62
+ },
63
+ "additionalProperties": false
64
+ },
65
+ "riskStepClassification": {
66
+ "type": "object",
67
+ "required": ["stepId", "categories", "reviewRequired", "failureModes", "requiredEvidence"],
68
+ "properties": {
69
+ "stepId": { "$ref": "#/$defs/nonEmptyString" },
70
+ "categories": { "type": "array", "uniqueItems": true, "items": { "enum": ["migration", "concurrency", "restart-replay", "crash-window", "external-provider", "runtime-composition", "security-boundary", "auth-secrets", "irreversible-execution"] } },
71
+ "reviewRequired": { "type": "boolean" },
72
+ "failureModes": { "$ref": "#/$defs/stringArray" },
73
+ "requiredEvidence": { "$ref": "#/$defs/stringArray" },
74
+ "proofObligations": { "type": "array", "items": { "$ref": "#/$defs/riskProofObligation" } }
75
+ },
76
+ "additionalProperties": false
77
+ },
78
+ "riskProofObligation": {
79
+ "type": "object",
80
+ "required": ["failureMode", "counterexample", "expectedResult", "evidence"],
81
+ "properties": {
82
+ "failureMode": { "$ref": "#/$defs/nonEmptyString" },
83
+ "counterexample": { "$ref": "#/$defs/nonEmptyString" },
84
+ "expectedResult": { "$ref": "#/$defs/nonEmptyString" },
85
+ "evidence": { "$ref": "#/$defs/nonEmptyString" }
86
+ },
87
+ "additionalProperties": false
88
+ },
89
+ "correctiveReplanSnapshot": {
90
+ "type": "object",
91
+ "required": ["taskId", "taskRevision", "taskStatus", "currentPlanHash", "stepId", "stepStatus", "inProgressStepIds", "remediationAttemptCount", "correctivePosture", "correctiveDecision", "candidatePlanHash", "candidateStepIds", "actorRoleEligible", "c1ClaimAbsent", "writerLeaseAbsent", "planValidationPassed", "receiptValid", "riskAuditPassed"],
92
+ "properties": {
93
+ "taskId": { "$ref": "#/$defs/taskId" },
94
+ "taskRevision": { "$ref": "#/$defs/expectedRevision" },
95
+ "taskStatus": { "type": "string" },
96
+ "currentPlanHash": { "type": ["string", "null"] },
97
+ "stepId": { "type": "string", "minLength": 1 },
98
+ "stepStatus": { "type": ["string", "null"] },
99
+ "inProgressStepIds": { "type": "array", "items": { "type": "string" } },
100
+ "remediationAttemptCount": { "type": "integer", "minimum": 0 },
101
+ "correctivePosture": { "type": "string" },
102
+ "correctiveDecision": { "type": ["object", "null"] },
103
+ "candidatePlanHash": { "type": ["string", "null"] },
104
+ "candidateStepIds": { "type": ["array", "null"], "items": { "type": "string" } }
105
+ ,"actorRoleEligible": { "type": "boolean" }
106
+ ,"c1ClaimAbsent": { "type": "boolean" }
107
+ ,"writerLeaseAbsent": { "type": "boolean" }
108
+ ,"planValidationPassed": { "type": "boolean" }
109
+ ,"receiptValid": { "type": "boolean" }
110
+ ,"riskAuditPassed": { "type": "boolean" }
111
+ },
112
+ "additionalProperties": false
113
+ },
114
+ "correctiveReplanBindings": {
115
+ "type": "object",
116
+ "required": ["taskId", "expectedRevision", "currentPlanHash", "stepId", "correctiveDecisionEventId"],
117
+ "properties": {
118
+ "taskId": { "$ref": "#/$defs/taskId" },
119
+ "expectedRevision": { "$ref": "#/$defs/expectedRevision" },
120
+ "currentPlanHash": { "type": "string", "minLength": 1 },
121
+ "stepId": { "type": "string", "minLength": 1 },
122
+ "correctiveDecisionEventId": { "type": "string", "minLength": 1 },
123
+ "candidatePlanHash": { "type": "string", "minLength": 1 },
124
+ "candidateStepIds": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }
125
+ },
126
+ "additionalProperties": false
127
+ },
128
+ "taskStateReadback": {
129
+ "type": "object",
130
+ "required": ["id", "revision", "status", "planHash"],
131
+ "properties": {
132
+ "id": { "$ref": "#/$defs/taskId" },
133
+ "revision": { "$ref": "#/$defs/expectedRevision" },
134
+ "status": { "type": "string" },
135
+ "planHash": { "type": ["string", "null"] }
136
+ },
137
+ "additionalProperties": true
138
+ }
139
+ }
140
+ }
@@ -0,0 +1,512 @@
1
+ #!/usr/bin/env python3
2
+ """Generate the tracked Russian Workflow V2 PDF documentation.
3
+
4
+ The Markdown sources under docs/pdf/sources are the reviewable authority for the
5
+ three human-facing PDF artifacts. The generator is deterministic so release
6
+ validation can compare regenerated bytes with the committed files.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import hashlib
13
+ import html
14
+ import json
15
+ import re
16
+ import sys
17
+ import tempfile
18
+ from pathlib import Path
19
+
20
+ from reportlab.lib import colors
21
+ from reportlab.lib.enums import TA_CENTER, TA_LEFT
22
+ from reportlab.lib.pagesizes import A4
23
+ from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
24
+ from reportlab.lib.units import mm
25
+ from reportlab.pdfbase import pdfmetrics
26
+ from reportlab.pdfbase.ttfonts import TTFont
27
+ from reportlab.pdfgen import canvas
28
+ from reportlab.platypus import (
29
+ KeepTogether,
30
+ ListFlowable,
31
+ ListItem,
32
+ LongTable,
33
+ PageBreak,
34
+ Paragraph,
35
+ SimpleDocTemplate,
36
+ Spacer,
37
+ Table,
38
+ TableStyle,
39
+ )
40
+
41
+
42
+ ROOT = Path(__file__).resolve().parents[1]
43
+ SOURCE_DIR = ROOT / "docs" / "pdf" / "sources"
44
+ OUTPUT_DIR = ROOT / "docs" / "pdf"
45
+ PACKAGE = json.loads((ROOT / "package.json").read_text(encoding="utf-8"))
46
+ PACKAGE_VERSION = PACKAGE["version"]
47
+
48
+ DOCUMENTS = (
49
+ ("codex-workflow-v2-architecture-ru.md", "codex-workflow-v2-architecture-ru.pdf"),
50
+ ("codex-workflow-v2-chat-only-guide-ru.md", "codex-workflow-v2-chat-only-guide-ru.pdf"),
51
+ ("codex-workflow-v2-technical-reference-ru.md", "codex-workflow-v2-technical-reference-ru.pdf"),
52
+ )
53
+
54
+ PAGE_WIDTH, PAGE_HEIGHT = A4
55
+ LEFT = 22 * mm
56
+ RIGHT = 20 * mm
57
+ TOP = 22 * mm
58
+ BOTTOM = 18 * mm
59
+ CONTENT_WIDTH = PAGE_WIDTH - LEFT - RIGHT
60
+
61
+ NAVY = colors.HexColor("#14213D")
62
+ BLUE = colors.HexColor("#2563EB")
63
+ PALE_BLUE = colors.HexColor("#EFF6FF")
64
+ PALE_GREEN = colors.HexColor("#ECFDF5")
65
+ PALE_ORANGE = colors.HexColor("#FFF7ED")
66
+ PALE_GRAY = colors.HexColor("#F8FAFC")
67
+ MID_GRAY = colors.HexColor("#CBD5E1")
68
+ TEXT = colors.HexColor("#172033")
69
+ MUTED = colors.HexColor("#64748B")
70
+
71
+
72
+ def first_existing(candidates: tuple[str, ...]) -> str:
73
+ for candidate in candidates:
74
+ if Path(candidate).is_file():
75
+ return candidate
76
+ raise RuntimeError(f"No usable font found in: {', '.join(candidates)}")
77
+
78
+
79
+ def register_fonts() -> None:
80
+ regular = first_existing((
81
+ "/System/Library/Fonts/Supplemental/Arial.ttf",
82
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
83
+ "/usr/share/fonts/dejavu/DejaVuSans.ttf",
84
+ ))
85
+ bold = first_existing((
86
+ "/System/Library/Fonts/Supplemental/Arial Bold.ttf",
87
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
88
+ "/usr/share/fonts/dejavu/DejaVuSans-Bold.ttf",
89
+ ))
90
+ italic = first_existing((
91
+ "/System/Library/Fonts/Supplemental/Arial Italic.ttf",
92
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans-Oblique.ttf",
93
+ "/usr/share/fonts/dejavu/DejaVuSans-Oblique.ttf",
94
+ ))
95
+ bold_italic = first_existing((
96
+ "/System/Library/Fonts/Supplemental/Arial Bold Italic.ttf",
97
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans-BoldOblique.ttf",
98
+ "/usr/share/fonts/dejavu/DejaVuSans-BoldOblique.ttf",
99
+ ))
100
+ pdfmetrics.registerFont(TTFont("WorkflowBody", regular))
101
+ pdfmetrics.registerFont(TTFont("WorkflowBold", bold))
102
+ pdfmetrics.registerFont(TTFont("WorkflowItalic", italic))
103
+ pdfmetrics.registerFont(TTFont("WorkflowBoldItalic", bold_italic))
104
+ pdfmetrics.registerFontFamily(
105
+ "WorkflowBody",
106
+ normal="WorkflowBody",
107
+ bold="WorkflowBold",
108
+ italic="WorkflowItalic",
109
+ boldItalic="WorkflowBoldItalic",
110
+ )
111
+
112
+
113
+ class InvariantCanvas(canvas.Canvas):
114
+ def __init__(self, *args, **kwargs):
115
+ kwargs["invariant"] = 1
116
+ super().__init__(*args, **kwargs)
117
+
118
+
119
+ def styles() -> dict[str, ParagraphStyle]:
120
+ base = getSampleStyleSheet()
121
+ return {
122
+ "title": ParagraphStyle(
123
+ "Title",
124
+ parent=base["Title"],
125
+ fontName="WorkflowBold",
126
+ fontSize=25,
127
+ leading=30,
128
+ textColor=NAVY,
129
+ alignment=TA_LEFT,
130
+ spaceAfter=10,
131
+ ),
132
+ "subtitle": ParagraphStyle(
133
+ "Subtitle",
134
+ parent=base["Normal"],
135
+ fontName="WorkflowBody",
136
+ fontSize=12,
137
+ leading=17,
138
+ textColor=MUTED,
139
+ spaceAfter=18,
140
+ ),
141
+ "part": ParagraphStyle(
142
+ "Part",
143
+ parent=base["Normal"],
144
+ fontName="WorkflowBold",
145
+ fontSize=8,
146
+ leading=10,
147
+ textColor=BLUE,
148
+ spaceAfter=10,
149
+ ),
150
+ "h1": ParagraphStyle(
151
+ "H1",
152
+ parent=base["Heading1"],
153
+ fontName="WorkflowBold",
154
+ fontSize=18,
155
+ leading=22,
156
+ textColor=NAVY,
157
+ spaceBefore=10,
158
+ spaceAfter=8,
159
+ keepWithNext=True,
160
+ ),
161
+ "h2": ParagraphStyle(
162
+ "H2",
163
+ parent=base["Heading2"],
164
+ fontName="WorkflowBold",
165
+ fontSize=14,
166
+ leading=18,
167
+ textColor=BLUE,
168
+ spaceBefore=8,
169
+ spaceAfter=6,
170
+ keepWithNext=True,
171
+ ),
172
+ "h3": ParagraphStyle(
173
+ "H3",
174
+ parent=base["Heading3"],
175
+ fontName="WorkflowBold",
176
+ fontSize=11,
177
+ leading=14,
178
+ textColor=NAVY,
179
+ spaceBefore=6,
180
+ spaceAfter=4,
181
+ keepWithNext=True,
182
+ ),
183
+ "body": ParagraphStyle(
184
+ "Body",
185
+ parent=base["BodyText"],
186
+ fontName="WorkflowBody",
187
+ fontSize=9.1,
188
+ leading=13.2,
189
+ textColor=TEXT,
190
+ spaceAfter=5,
191
+ allowWidows=0,
192
+ allowOrphans=0,
193
+ ),
194
+ "small": ParagraphStyle(
195
+ "Small",
196
+ parent=base["BodyText"],
197
+ fontName="WorkflowBody",
198
+ fontSize=7.7,
199
+ leading=10.3,
200
+ textColor=TEXT,
201
+ ),
202
+ "code": ParagraphStyle(
203
+ "Code",
204
+ parent=base["BodyText"],
205
+ fontName="WorkflowBody",
206
+ fontSize=7.6,
207
+ leading=10.1,
208
+ textColor=colors.HexColor("#E2E8F0"),
209
+ backColor=NAVY,
210
+ borderPadding=0,
211
+ wordWrap="CJK",
212
+ ),
213
+ "callout": ParagraphStyle(
214
+ "Callout",
215
+ parent=base["BodyText"],
216
+ fontName="WorkflowBody",
217
+ fontSize=8.5,
218
+ leading=12,
219
+ textColor=TEXT,
220
+ ),
221
+ "footer": ParagraphStyle(
222
+ "Footer",
223
+ parent=base["Normal"],
224
+ fontName="WorkflowBody",
225
+ fontSize=7,
226
+ textColor=MUTED,
227
+ alignment=TA_CENTER,
228
+ ),
229
+ }
230
+
231
+
232
+ def inline_markup(value: str) -> str:
233
+ escaped = html.escape(value.strip())
234
+ escaped = re.sub(r"`([^`]+)`", r'<font name="WorkflowBold">\1</font>', escaped)
235
+ escaped = re.sub(r"\*\*([^*]+)\*\*", r"<b>\1</b>", escaped)
236
+ escaped = re.sub(r"\*([^*]+)\*", r"<i>\1</i>", escaped)
237
+ return escaped
238
+
239
+
240
+ def parse_source(path: Path) -> tuple[dict[str, str], list[str]]:
241
+ lines = path.read_text(encoding="utf-8").splitlines()
242
+ if not lines or lines[0].strip() != "---":
243
+ raise RuntimeError(f"{path} must start with frontmatter")
244
+ metadata: dict[str, str] = {}
245
+ index = 1
246
+ while index < len(lines) and lines[index].strip() != "---":
247
+ line = lines[index]
248
+ if ":" not in line:
249
+ raise RuntimeError(f"Invalid frontmatter line in {path}: {line}")
250
+ key, value = line.split(":", 1)
251
+ metadata[key.strip()] = value.strip().strip('"')
252
+ index += 1
253
+ if index >= len(lines):
254
+ raise RuntimeError(f"Unclosed frontmatter in {path}")
255
+ return metadata, lines[index + 1 :]
256
+
257
+
258
+ def table_widths(column_count: int) -> list[float]:
259
+ if column_count == 2:
260
+ return [CONTENT_WIDTH * 0.34, CONTENT_WIDTH * 0.66]
261
+ if column_count == 3:
262
+ return [CONTENT_WIDTH * 0.26, CONTENT_WIDTH * 0.34, CONTENT_WIDTH * 0.40]
263
+ return [CONTENT_WIDTH / column_count] * column_count
264
+
265
+
266
+ def markdown_story(lines: list[str], style: dict[str, ParagraphStyle]) -> list:
267
+ story: list = []
268
+ index = 0
269
+ while index < len(lines):
270
+ raw = lines[index]
271
+ stripped = raw.strip()
272
+ if not stripped:
273
+ story.append(Spacer(1, 2.5))
274
+ index += 1
275
+ continue
276
+ if stripped == "<!-- pagebreak -->":
277
+ story.append(PageBreak())
278
+ index += 1
279
+ continue
280
+ if stripped.startswith("```"):
281
+ code_lines: list[str] = []
282
+ index += 1
283
+ while index < len(lines) and not lines[index].strip().startswith("```"):
284
+ code_lines.append(lines[index].rstrip())
285
+ index += 1
286
+ if index >= len(lines):
287
+ raise RuntimeError("Unclosed code block")
288
+ code_html = "<br/>".join(html.escape(line or " ") for line in code_lines)
289
+ block = Table([[Paragraph(code_html, style["code"])]], colWidths=[CONTENT_WIDTH])
290
+ block.setStyle(TableStyle([
291
+ ("BACKGROUND", (0, 0), (-1, -1), NAVY),
292
+ ("BOX", (0, 0), (-1, -1), 0.8, BLUE),
293
+ ("LEFTPADDING", (0, 0), (-1, -1), 10),
294
+ ("RIGHTPADDING", (0, 0), (-1, -1), 10),
295
+ ("TOPPADDING", (0, 0), (-1, -1), 8),
296
+ ("BOTTOMPADDING", (0, 0), (-1, -1), 8),
297
+ ]))
298
+ story.extend([block, Spacer(1, 6)])
299
+ index += 1
300
+ continue
301
+ heading = re.match(r"^(#{1,3})\s+(.+)$", stripped)
302
+ if heading:
303
+ level = len(heading.group(1))
304
+ story.append(Paragraph(inline_markup(heading.group(2)), style[f"h{level}"]))
305
+ index += 1
306
+ continue
307
+ if stripped.startswith("|"):
308
+ table_lines: list[str] = []
309
+ while index < len(lines) and lines[index].strip().startswith("|"):
310
+ table_lines.append(lines[index].strip())
311
+ index += 1
312
+ rows = [[cell.strip() for cell in line.strip("|").split("|")] for line in table_lines]
313
+ if len(rows) >= 2 and all(re.fullmatch(r":?-{3,}:?", cell) for cell in rows[1]):
314
+ rows.pop(1)
315
+ column_count = max(len(row) for row in rows)
316
+ normalized = [row + [""] * (column_count - len(row)) for row in rows]
317
+ rendered = [
318
+ [Paragraph(inline_markup(cell), style["small"]) for cell in row]
319
+ for row in normalized
320
+ ]
321
+ table = LongTable(rendered, colWidths=table_widths(column_count), repeatRows=1)
322
+ table.setStyle(TableStyle([
323
+ ("BACKGROUND", (0, 0), (-1, 0), NAVY),
324
+ ("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
325
+ ("FONTNAME", (0, 0), (-1, 0), "WorkflowBold"),
326
+ ("BACKGROUND", (0, 1), (-1, -1), PALE_GRAY),
327
+ ("GRID", (0, 0), (-1, -1), 0.45, MID_GRAY),
328
+ ("VALIGN", (0, 0), (-1, -1), "TOP"),
329
+ ("LEFTPADDING", (0, 0), (-1, -1), 6),
330
+ ("RIGHTPADDING", (0, 0), (-1, -1), 6),
331
+ ("TOPPADDING", (0, 0), (-1, -1), 5),
332
+ ("BOTTOMPADDING", (0, 0), (-1, -1), 5),
333
+ ]))
334
+ story.extend([table, Spacer(1, 6)])
335
+ continue
336
+ if stripped.startswith(">"):
337
+ quote_lines: list[str] = []
338
+ while index < len(lines) and lines[index].strip().startswith(">"):
339
+ quote_lines.append(lines[index].strip()[1:].strip())
340
+ index += 1
341
+ value = " ".join(quote_lines)
342
+ color = PALE_ORANGE if "Важно" in value or "Стоп" in value else PALE_BLUE
343
+ callout = Table([[Paragraph(inline_markup(value), style["callout"])]], colWidths=[CONTENT_WIDTH])
344
+ callout.setStyle(TableStyle([
345
+ ("BACKGROUND", (0, 0), (-1, -1), color),
346
+ ("BOX", (0, 0), (-1, -1), 0.8, BLUE),
347
+ ("LINEBEFORE", (0, 0), (0, -1), 3, BLUE),
348
+ ("LEFTPADDING", (0, 0), (-1, -1), 10),
349
+ ("RIGHTPADDING", (0, 0), (-1, -1), 10),
350
+ ("TOPPADDING", (0, 0), (-1, -1), 7),
351
+ ("BOTTOMPADDING", (0, 0), (-1, -1), 7),
352
+ ]))
353
+ story.extend([callout, Spacer(1, 6)])
354
+ continue
355
+ if re.match(r"^-\s+", stripped):
356
+ items: list[ListItem] = []
357
+ while index < len(lines) and re.match(r"^-\s+", lines[index].strip()):
358
+ value = re.sub(r"^-\s+", "", lines[index].strip())
359
+ items.append(ListItem(Paragraph(inline_markup(value), style["body"]), leftIndent=10))
360
+ index += 1
361
+ story.append(ListFlowable(items, bulletType="bullet", leftIndent=16, bulletColor=BLUE))
362
+ story.append(Spacer(1, 3))
363
+ continue
364
+ if re.match(r"^\d+\.\s+", stripped):
365
+ items = []
366
+ while index < len(lines) and re.match(r"^\d+\.\s+", lines[index].strip()):
367
+ value = re.sub(r"^\d+\.\s+", "", lines[index].strip())
368
+ items.append(ListItem(Paragraph(inline_markup(value), style["body"]), leftIndent=12))
369
+ index += 1
370
+ story.append(ListFlowable(items, bulletType="1", leftIndent=18, bulletColor=BLUE))
371
+ story.append(Spacer(1, 3))
372
+ continue
373
+
374
+ paragraph_lines = [stripped]
375
+ index += 1
376
+ while index < len(lines):
377
+ candidate = lines[index].strip()
378
+ if (
379
+ not candidate
380
+ or candidate == "<!-- pagebreak -->"
381
+ or candidate.startswith(("#", "|", ">", "```", "- "))
382
+ or re.match(r"^\d+\.\s+", candidate)
383
+ ):
384
+ break
385
+ paragraph_lines.append(candidate)
386
+ index += 1
387
+ story.append(Paragraph(inline_markup(" ".join(paragraph_lines)), style["body"]))
388
+ return story
389
+
390
+
391
+ def draw_page(canvas_obj: canvas.Canvas, doc: SimpleDocTemplate, title: str) -> None:
392
+ canvas_obj.saveState()
393
+ canvas_obj.setStrokeColor(MID_GRAY)
394
+ canvas_obj.setLineWidth(0.35)
395
+ canvas_obj.line(LEFT, PAGE_HEIGHT - 13 * mm, PAGE_WIDTH - RIGHT, PAGE_HEIGHT - 13 * mm)
396
+ canvas_obj.setFont("WorkflowBody", 7)
397
+ canvas_obj.setFillColor(MUTED)
398
+ canvas_obj.drawString(LEFT, PAGE_HEIGHT - 10 * mm, title[:74])
399
+ canvas_obj.drawRightString(PAGE_WIDTH - RIGHT, PAGE_HEIGHT - 10 * mm, f"beta.11 | {PACKAGE_VERSION}")
400
+ canvas_obj.line(LEFT, 11 * mm, PAGE_WIDTH - RIGHT, 11 * mm)
401
+ canvas_obj.drawString(LEFT, 7 * mm, "Codex Workflow V2")
402
+ canvas_obj.drawRightString(PAGE_WIDTH - RIGHT, 7 * mm, f"Страница {doc.page}")
403
+ canvas_obj.restoreState()
404
+
405
+
406
+ def build_pdf(source_path: Path, output_path: Path) -> None:
407
+ metadata, lines = parse_source(source_path)
408
+ required = ("title", "subtitle", "part", "document_version", "date", "subject")
409
+ missing = [key for key in required if key not in metadata]
410
+ if missing:
411
+ raise RuntimeError(f"Missing metadata in {source_path}: {', '.join(missing)}")
412
+ style = styles()
413
+ output_path.parent.mkdir(parents=True, exist_ok=True)
414
+ doc = SimpleDocTemplate(
415
+ str(output_path),
416
+ pagesize=A4,
417
+ leftMargin=LEFT,
418
+ rightMargin=RIGHT,
419
+ topMargin=TOP,
420
+ bottomMargin=BOTTOM,
421
+ title=metadata["title"],
422
+ author="Codex Workflow Maintainers",
423
+ subject=metadata["subject"],
424
+ creator="codex-workflow-v2 deterministic PDF generator",
425
+ pageCompression=1,
426
+ )
427
+ title_block = [
428
+ Spacer(1, 28 * mm),
429
+ Paragraph(inline_markup(metadata["part"].upper()), style["part"]),
430
+ Paragraph(inline_markup(metadata["title"]), style["title"]),
431
+ Paragraph(inline_markup(metadata["subtitle"]), style["subtitle"]),
432
+ Spacer(1, 8 * mm),
433
+ Table(
434
+ [[
435
+ Paragraph(
436
+ inline_markup(
437
+ f"Документ {metadata['document_version']} | Пакет {PACKAGE_VERSION} | {metadata['date']}"
438
+ ),
439
+ style["callout"],
440
+ )
441
+ ]],
442
+ colWidths=[CONTENT_WIDTH],
443
+ style=TableStyle([
444
+ ("BACKGROUND", (0, 0), (-1, -1), PALE_BLUE),
445
+ ("BOX", (0, 0), (-1, -1), 0.8, BLUE),
446
+ ("LEFTPADDING", (0, 0), (-1, -1), 10),
447
+ ("RIGHTPADDING", (0, 0), (-1, -1), 10),
448
+ ("TOPPADDING", (0, 0), (-1, -1), 8),
449
+ ("BOTTOMPADDING", (0, 0), (-1, -1), 8),
450
+ ]),
451
+ ),
452
+ Spacer(1, 55 * mm),
453
+ Paragraph(
454
+ inline_markup("Целевая среда: Codex App, один пользователь, одна машина, exact project-local package."),
455
+ style["small"],
456
+ ),
457
+ PageBreak(),
458
+ ]
459
+ story = title_block + markdown_story(lines, style)
460
+ doc.build(
461
+ story,
462
+ onFirstPage=lambda canv, current_doc: draw_page(canv, current_doc, metadata["title"]),
463
+ onLaterPages=lambda canv, current_doc: draw_page(canv, current_doc, metadata["title"]),
464
+ canvasmaker=InvariantCanvas,
465
+ )
466
+
467
+
468
+ def sha256(path: Path) -> str:
469
+ return hashlib.sha256(path.read_bytes()).hexdigest()
470
+
471
+
472
+ def generate(output_dir: Path) -> None:
473
+ for source_name, output_name in DOCUMENTS:
474
+ build_pdf(SOURCE_DIR / source_name, output_dir / output_name)
475
+
476
+
477
+ def check() -> int:
478
+ with tempfile.TemporaryDirectory(prefix="codex-workflow-pdf-") as directory:
479
+ candidate_dir = Path(directory)
480
+ generate(candidate_dir)
481
+ stale = []
482
+ for _, output_name in DOCUMENTS:
483
+ tracked = OUTPUT_DIR / output_name
484
+ candidate = candidate_dir / output_name
485
+ if not tracked.is_file() or tracked.read_bytes() != candidate.read_bytes():
486
+ stale.append({
487
+ "file": str(tracked.relative_to(ROOT)),
488
+ "trackedSha256": sha256(tracked) if tracked.is_file() else None,
489
+ "generatedSha256": sha256(candidate),
490
+ })
491
+ print(json.dumps({"ok": not stale, "packageVersion": PACKAGE_VERSION, "stale": stale}, ensure_ascii=False))
492
+ return 0 if not stale else 1
493
+
494
+
495
+ def main() -> int:
496
+ parser = argparse.ArgumentParser(description=__doc__)
497
+ parser.add_argument("--check", action="store_true", help="fail when tracked PDFs differ from deterministic output")
498
+ args = parser.parse_args()
499
+ register_fonts()
500
+ if args.check:
501
+ return check()
502
+ generate(OUTPUT_DIR)
503
+ print(json.dumps({
504
+ "ok": True,
505
+ "packageVersion": PACKAGE_VERSION,
506
+ "outputs": [str((OUTPUT_DIR / output).relative_to(ROOT)) for _, output in DOCUMENTS],
507
+ }, ensure_ascii=False))
508
+ return 0
509
+
510
+
511
+ if __name__ == "__main__":
512
+ sys.exit(main())