@afokapu/atdd-bun 0.6.2 → 0.7.1

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 (95) hide show
  1. package/README.md +13 -3
  2. package/conventions/coder.bun/coder.bun.telemetry-forbidden-properties.convention.yaml +51 -0
  3. package/conventions/coder.bun/coder.bun.telemetry-implementation-binding.convention.yaml +45 -0
  4. package/conventions/coder.bun/coder.bun.telemetry-raw-string-emit.convention.yaml +53 -0
  5. package/conventions/coder.bun/coder.bun.telemetry-source-binding.convention.yaml +48 -0
  6. package/conventions/coder.bun/coder.bun.telemetry-vendor-sdk.convention.yaml +51 -0
  7. package/conventions/planner.telemetry/planner.telemetry.acceptance-decision.convention.yaml +58 -0
  8. package/conventions/planner.telemetry/planner.telemetry.logical-ownership.convention.yaml +50 -0
  9. package/conventions/planner.telemetry/planner.telemetry.metric-cardinality.convention.yaml +47 -0
  10. package/conventions/planner.telemetry/planner.telemetry.tracking-plan-schema.convention.yaml +75 -0
  11. package/conventions/tester.bun/tester.bun.telemetry-captured-sink.convention.yaml +47 -0
  12. package/conventions/tester.bun/tester.bun.telemetry-identity-assertion.convention.yaml +44 -0
  13. package/conventions/tester.bun/tester.bun.telemetry-required-item-coverage.convention.yaml +44 -0
  14. package/conventions/tester.bun/tester.bun.telemetry-test-binding.convention.yaml +52 -0
  15. package/conventions/tester.bun/tester.bun.telemetry-timing-semantics.convention.yaml +53 -0
  16. package/detectors/bun_telemetry_code/atdd.implementation.yaml +24 -0
  17. package/detectors/bun_telemetry_code/calls.mjs +104 -0
  18. package/detectors/bun_telemetry_code/checks/t_forbidden_properties.mjs +46 -0
  19. package/detectors/bun_telemetry_code/checks/t_implementation_binding.mjs +41 -0
  20. package/detectors/bun_telemetry_code/checks/t_raw_string_emit.mjs +34 -0
  21. package/detectors/bun_telemetry_code/checks/t_source_binding.mjs +48 -0
  22. package/detectors/bun_telemetry_code/checks/t_vendor_sdk.mjs +38 -0
  23. package/detectors/bun_telemetry_code/detect.mjs +50 -0
  24. package/detectors/bun_telemetry_code/fixtures/clean/plan/commons/E001.yaml +10 -0
  25. package/detectors/bun_telemetry_code/fixtures/clean/plan/commons/E002.yaml +7 -0
  26. package/detectors/bun_telemetry_code/fixtures/clean/plan/commons/_commons.yaml +6 -0
  27. package/detectors/bun_telemetry_code/fixtures/clean/src/wagons/commons/features/ingress/domain/accept-response.ts +8 -0
  28. package/detectors/bun_telemetry_code/fixtures/clean/src/wagons/commons/features/ingress/infrastructure/otel-adapter.ts +8 -0
  29. package/detectors/bun_telemetry_code/fixtures/clean/telemetry/commons/response-invocation-accepted/event.be.json +16 -0
  30. package/detectors/bun_telemetry_code/fixtures/clean/telemetry/commons/response-invocation-accepted/metric.be.duration.json +19 -0
  31. package/detectors/bun_telemetry_code/fixtures/clean/tests/wagons/commons/features/ingress/unit/accept-response.telemetry.test.ts +13 -0
  32. package/detectors/bun_telemetry_code/fixtures/clean/tests/wagons/commons/features/ingress/unit/accept-response.test.ts +9 -0
  33. package/detectors/bun_telemetry_code/fixtures/dirty/plan/commons/E001.yaml +8 -0
  34. package/detectors/bun_telemetry_code/fixtures/dirty/plan/commons/_commons.yaml +6 -0
  35. package/detectors/bun_telemetry_code/fixtures/dirty/src/wagons/commons/features/ingress/domain/accept-response.ts +13 -0
  36. package/detectors/bun_telemetry_code/fixtures/dirty/src/wagons/commons/features/ingress/domain/tracing.ts +3 -0
  37. package/detectors/bun_telemetry_code/fixtures/dirty/telemetry/commons/response-invocation-accepted/event.be.json +16 -0
  38. package/detectors/bun_telemetry_code/fixtures/dirty/tests/wagons/commons/features/ingress/unit/probe.test.ts +7 -0
  39. package/detectors/bun_telemetry_code/registry.mjs +34 -0
  40. package/detectors/bun_telemetry_test/_shared.mjs +92 -0
  41. package/detectors/bun_telemetry_test/atdd.implementation.yaml +24 -0
  42. package/detectors/bun_telemetry_test/checks/t_captured_sink.mjs +35 -0
  43. package/detectors/bun_telemetry_test/checks/t_identity_assertion.mjs +45 -0
  44. package/detectors/bun_telemetry_test/checks/t_required_item_coverage.mjs +37 -0
  45. package/detectors/bun_telemetry_test/checks/t_test_binding.mjs +56 -0
  46. package/detectors/bun_telemetry_test/checks/t_timing_semantics.mjs +51 -0
  47. package/detectors/bun_telemetry_test/detect.mjs +50 -0
  48. package/detectors/bun_telemetry_test/fixtures/clean/plan/commons/E001.yaml +10 -0
  49. package/detectors/bun_telemetry_test/fixtures/clean/plan/commons/E002.yaml +7 -0
  50. package/detectors/bun_telemetry_test/fixtures/clean/plan/commons/_commons.yaml +6 -0
  51. package/detectors/bun_telemetry_test/fixtures/clean/src/wagons/commons/features/ingress/domain/accept-response.ts +8 -0
  52. package/detectors/bun_telemetry_test/fixtures/clean/telemetry/commons/response-invocation-accepted/event.be.json +16 -0
  53. package/detectors/bun_telemetry_test/fixtures/clean/telemetry/commons/response-invocation-accepted/metric.be.duration.json +19 -0
  54. package/detectors/bun_telemetry_test/fixtures/clean/tests/wagons/commons/features/ingress/unit/accept-response.telemetry.test.ts +13 -0
  55. package/detectors/bun_telemetry_test/fixtures/clean/tests/wagons/commons/features/ingress/unit/accept-response.test.ts +7 -0
  56. package/detectors/bun_telemetry_test/fixtures/dirty/plan/commons/E001.yaml +10 -0
  57. package/detectors/bun_telemetry_test/fixtures/dirty/plan/commons/E002.yaml +7 -0
  58. package/detectors/bun_telemetry_test/fixtures/dirty/plan/commons/_commons.yaml +6 -0
  59. package/detectors/bun_telemetry_test/fixtures/dirty/src/wagons/commons/features/ingress/domain/accept-response.ts +8 -0
  60. package/detectors/bun_telemetry_test/fixtures/dirty/telemetry/commons/response-invocation-accepted/event.be.json +16 -0
  61. package/detectors/bun_telemetry_test/fixtures/dirty/telemetry/commons/response-invocation-accepted/metric.be.duration.json +19 -0
  62. package/detectors/bun_telemetry_test/fixtures/dirty/tests/wagons/commons/features/ingress/unit/dangling.telemetry.test.ts +9 -0
  63. package/detectors/bun_telemetry_test/fixtures/dirty/tests/wagons/commons/features/ingress/unit/loose.telemetry.test.ts +11 -0
  64. package/detectors/bun_telemetry_test/fixtures/dirty/tests/wagons/commons/features/ingress/unit/unbound.telemetry.test.ts +10 -0
  65. package/detectors/planner_telemetry_plan/atdd.implementation.yaml +22 -0
  66. package/detectors/planner_telemetry_plan/detect.mjs +9 -0
  67. package/detectors/planner_telemetry_plan/fixtures/clean/plan/commons/E001.yaml +10 -0
  68. package/detectors/planner_telemetry_plan/fixtures/clean/plan/commons/E002.yaml +7 -0
  69. package/detectors/planner_telemetry_plan/fixtures/clean/plan/commons/_commons.yaml +6 -0
  70. package/detectors/planner_telemetry_plan/fixtures/clean/src/wagons/commons/features/ingress/domain/accept-response.ts +8 -0
  71. package/detectors/planner_telemetry_plan/fixtures/clean/telemetry/commons/response-invocation-accepted/event.be.json +16 -0
  72. package/detectors/planner_telemetry_plan/fixtures/clean/telemetry/commons/response-invocation-accepted/metric.be.duration.json +19 -0
  73. package/detectors/planner_telemetry_plan/fixtures/clean/tests/wagons/commons/features/ingress/unit/accept-response.telemetry.test.ts +13 -0
  74. package/detectors/planner_telemetry_plan/fixtures/dirty/plan/commons/E001.yaml +9 -0
  75. package/detectors/planner_telemetry_plan/fixtures/dirty/plan/commons/E002.yaml +4 -0
  76. package/detectors/planner_telemetry_plan/fixtures/dirty/plan/commons/E003.yaml +7 -0
  77. package/detectors/planner_telemetry_plan/fixtures/dirty/plan/commons/_commons.yaml +6 -0
  78. package/detectors/planner_telemetry_plan/fixtures/dirty/telemetry/commons/extra.json +1 -0
  79. package/detectors/planner_telemetry_plan/fixtures/dirty/telemetry/commons/orphan-artifact/event.be.json +14 -0
  80. package/detectors/planner_telemetry_plan/fixtures/dirty/telemetry/commons/response-invocation-accepted/event.be.json +16 -0
  81. package/detectors/planner_telemetry_plan/fixtures/dirty/telemetry/commons/response-invocation-accepted/metric.be.duration.json +22 -0
  82. package/detectors/planner_telemetry_plan/fixtures/dirty/telemetry/commons/response-invocation-accepted/notes.json +1 -0
  83. package/integrity.json +94 -11
  84. package/lib/scan.mjs +37 -0
  85. package/package.json +1 -1
  86. package/planner-schemas/acceptance.schema.json +70 -1
  87. package/planner-schemas/telemetry-plan.schema.json +133 -0
  88. package/relationships.yaml +174 -0
  89. package/src/enforce.ts +2 -1
  90. package/src/index.ts +2 -0
  91. package/src/integrity.ts +25 -9
  92. package/src/setup.ts +17 -1
  93. package/src/telemetry-plan.ts +223 -0
  94. package/src/topology.ts +3 -1
  95. package/templates/github/atdd-bun.yml +5 -0
@@ -0,0 +1,133 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Telemetry Tracking-Plan Item Schema",
4
+ "description": "Validates one concrete telemetry tracking-plan file under telemetry/<theme>/<artifact>/{kind}.{plane}[.{measure}].json. Local shape only: path mirroring, kind/plane/measure coherence with the file name, wagon ownership resolution and acceptance linkage are cross-artifact validator concerns (src/telemetry-plan.ts).",
5
+ "$comment": "Shipped by the telemetry profile (planner_telemetry_plan). The capability is deliberately inert until a repository adopts it: no telemetry/ root and no acceptance telemetry declaration means no findings. `signal` in acceptance.schema.json keeps the executable bar (signal.metric + signal.threshold); the descriptive arrays it also carries are superseded by the acceptance `telemetry` block and by this registry, and are not renamed here — that migration is a separate, documented step.",
6
+ "type": "object",
7
+ "required": ["id", "version", "logical_artifact", "kind", "plane", "owner", "purpose", "acceptances", "properties"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "id": {
11
+ "type": "string",
12
+ "pattern": "^telemetry:(event|trace|metric|log):(ui|ux|be|nw|db|st|tm|sc|au|fn|if):[a-z][a-z0-9-]*:[a-z][a-z0-9-]*(?::(latency|duration|throughput|error_rate|success_rate|count|size|age|staleness|freshness))?$",
13
+ "description": "Concrete telemetry URN: telemetry:{kind}:{plane}:{theme}:{artifact}[:{measure}]. Must mirror the file's path and name; the validator checks that coherence."
14
+ },
15
+ "version": {
16
+ "type": "string",
17
+ "pattern": "^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)$",
18
+ "description": "SemVer core for this item's contract. Changing properties, required or forbidden_properties is a contract change and must bump it."
19
+ },
20
+ "logical_artifact": {
21
+ "type": "string",
22
+ "pattern": "^telemetry:[a-z][a-z0-9-]*:[a-z][a-z0-9-]*$",
23
+ "description": "The stable architectural identity of the observable outcome: telemetry:{theme}:{artifact}. Not an exporter event. Must be owned by exactly one wagon's produce[] telemetry entry."
24
+ },
25
+ "kind": {
26
+ "type": "string",
27
+ "enum": ["event", "trace", "metric", "log"]
28
+ },
29
+ "plane": {
30
+ "type": "string",
31
+ "enum": ["ui", "ux", "be", "nw", "db", "st", "tm", "sc", "au", "fn", "if"]
32
+ },
33
+ "measure": {
34
+ "type": "string",
35
+ "enum": ["latency", "duration", "throughput", "error_rate", "success_rate", "count", "size", "age", "staleness", "freshness"],
36
+ "description": "Required for metric items (and only metric items); must match the measure segment of the file name and of the id."
37
+ },
38
+ "instrument": {
39
+ "type": "string",
40
+ "enum": ["counter", "gauge", "histogram", "summary"],
41
+ "description": "Metric instrument type per the telemetry kind vocabulary."
42
+ },
43
+ "unit": {
44
+ "type": "string",
45
+ "description": "Unit of measure for metric items (ms, s, bytes, percent, count)."
46
+ },
47
+ "owner": {
48
+ "type": "string",
49
+ "pattern": "^[a-z][a-z0-9-]*$",
50
+ "description": "The wagon slug that owns the logical artifact. The validator checks it equals the wagon whose produce[] declares the logical artifact."
51
+ },
52
+ "purpose": {
53
+ "type": "string",
54
+ "minLength": 20,
55
+ "description": "The question this item answers. A tracking plan is read by people deciding what to alert and dashboard on; an item that cannot state its question is not planned."
56
+ },
57
+ "acceptances": {
58
+ "type": "array",
59
+ "items": {
60
+ "type": "string",
61
+ "pattern": "^acc:[A-Za-z0-9._:-]+$",
62
+ "description": "Acceptance URNs that require this item. Each must resolve to a declared acceptance in the plan."
63
+ }
64
+ },
65
+ "properties": {
66
+ "type": "object",
67
+ "description": "The fields this item may carry, by name. `required` names must all appear here.",
68
+ "additionalProperties": {
69
+ "$ref": "#/definitions/property"
70
+ }
71
+ },
72
+ "required": {
73
+ "type": "array",
74
+ "items": {"type": "string", "minLength": 1},
75
+ "description": "Property names that must be present on every emission."
76
+ },
77
+ "forbidden_properties": {
78
+ "type": "array",
79
+ "items": {"type": "string", "minLength": 1},
80
+ "description": "Property names that must never be attached to this item (raw payloads, secrets, prompts)."
81
+ },
82
+ "dimensions": {
83
+ "type": "array",
84
+ "description": "Metric labels. Every dimension declares cardinality; high cardinality is forbidden for metrics — use an event, log or trace property instead.",
85
+ "items": {
86
+ "type": "object",
87
+ "required": ["name", "cardinality"],
88
+ "additionalProperties": false,
89
+ "properties": {
90
+ "name": {"type": "string", "minLength": 1},
91
+ "cardinality": {"type": "string", "enum": ["low", "medium", "high"]}
92
+ }
93
+ }
94
+ },
95
+ "timing": {
96
+ "type": "array",
97
+ "description": "Critical timing semantics a telemetry test bound to this item must exercise: post-commit (emitted only after the transaction commits), absent-after-rollback (never emitted when the transaction rolls back), correlation-across-async (correlation context continues across an async handoff). Declaring a semantic is what makes it testable; a test that cannot exercise it should not claim the item.",
98
+ "items": {"type": "string", "enum": ["post-commit", "absent-after-rollback", "correlation-across-async"]}
99
+ }
100
+ },
101
+ "definitions": {
102
+ "property": {
103
+ "type": "object",
104
+ "required": ["type"],
105
+ "additionalProperties": false,
106
+ "properties": {
107
+ "type": {
108
+ "type": "string",
109
+ "enum": ["string", "integer", "number", "boolean"]
110
+ },
111
+ "classification": {
112
+ "type": "string",
113
+ "enum": ["internal", "external", "pii"],
114
+ "description": "Who may see the value. PII forces a justification nobody has to reconstruct later."
115
+ },
116
+ "cardinality": {
117
+ "type": "string",
118
+ "enum": ["low", "medium", "high"],
119
+ "description": "Distinct-value volume. High cardinality is legal in event, log and trace properties where justified; never as a metric label."
120
+ },
121
+ "allowed_values": {
122
+ "type": "array",
123
+ "minItems": 1,
124
+ "items": {"type": ["string", "number", "boolean"]},
125
+ "description": "Closed vocabulary for this property."
126
+ },
127
+ "description": {
128
+ "type": "string"
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
@@ -81,6 +81,11 @@ nodes:
81
81
  - coder.bun.security-sql-injection
82
82
  - coder.bun.station-master-interlocking-routing
83
83
  - coder.bun.station-master-journey-routing
84
+ - coder.bun.telemetry-forbidden-properties
85
+ - coder.bun.telemetry-implementation-binding
86
+ - coder.bun.telemetry-raw-string-emit
87
+ - coder.bun.telemetry-source-binding
88
+ - coder.bun.telemetry-vendor-sdk
84
89
  - coder.bun.wagon-honours-its-contract
85
90
  - coder.htmx.fragment-escapes-interpolation
86
91
  - coder.htmx.swap-no-inline-handler
@@ -220,6 +225,10 @@ nodes:
220
225
  - planner.steps.linguistic-patterns
221
226
  - planner.steps.sequence
222
227
  - planner.subject.invariants
228
+ - planner.telemetry.acceptance-decision
229
+ - planner.telemetry.logical-ownership
230
+ - planner.telemetry.metric-cardinality
231
+ - planner.telemetry.tracking-plan-schema
223
232
  - planner.theme.definition
224
233
  - planner.theme.must-be-canonical
225
234
  - planner.theme.theme-zero-mandatory
@@ -310,7 +319,12 @@ nodes:
310
319
  - tester.bun.security-input
311
320
  - tester.bun.smoke-no-collaborator-substitution
312
321
  - tester.bun.smoke-observable-assertion
322
+ - tester.bun.telemetry-captured-sink
313
323
  - tester.bun.telemetry-emit
324
+ - tester.bun.telemetry-identity-assertion
325
+ - tester.bun.telemetry-required-item-coverage
326
+ - tester.bun.telemetry-test-binding
327
+ - tester.bun.telemetry-timing-semantics
314
328
  - tester.bun.test-carries-urn-identity
315
329
  - tester.bun.test-imports-bun-test
316
330
  - tester.bun.test-isolation-no-live-state
@@ -5012,3 +5026,163 @@ edges:
5012
5026
  target_ref: atdd-bun.planner.identity-required
5013
5027
  type: enables
5014
5028
  origin: "@afokapu/atdd-bun"
5029
+ - confidence: 1
5030
+ constraint: mandatory
5031
+ control: internal
5032
+ foundation: finish_to_start
5033
+ reason: the tracking-plan tree is the versioned realization of the telemetry URN-to-filesystem mapping
5034
+ source_ref: planner.telemetry.tracking-plan-schema
5035
+ strength: important
5036
+ target_ref: planner.wagon.telemetry-filesystem
5037
+ type: requires
5038
+ origin: "@afokapu/atdd-bun"
5039
+ - confidence: 1
5040
+ constraint: mandatory
5041
+ control: internal
5042
+ foundation: finish_to_start
5043
+ reason: "a tracking-plan item's path mirrors its concrete telemetry identity"
5044
+ source_ref: planner.telemetry.tracking-plan-schema
5045
+ strength: important
5046
+ target_ref: planner.artifact-naming.path-mirrors-identity
5047
+ type: follows
5048
+ origin: "@afokapu/atdd-bun"
5049
+ - confidence: 1
5050
+ constraint: mandatory
5051
+ control: internal
5052
+ foundation: finish_to_start
5053
+ reason: "wagon produce[] telemetry entries are the ownership declarations items resolve against"
5054
+ source_ref: planner.telemetry.logical-ownership
5055
+ strength: important
5056
+ target_ref: planner.wagon.produce-consume-artifacts
5057
+ type: requires
5058
+ origin: "@afokapu/atdd-bun"
5059
+ - confidence: 1
5060
+ constraint: mandatory
5061
+ control: internal
5062
+ foundation: finish_to_start
5063
+ reason: the disposition decision governs the telemetry URNs an acceptance may declare
5064
+ source_ref: planner.telemetry.acceptance-decision
5065
+ strength: important
5066
+ target_ref: planner.acceptance.signal-telemetry-naming
5067
+ type: requires
5068
+ origin: "@afokapu/atdd-bun"
5069
+ - confidence: 1
5070
+ constraint: mandatory
5071
+ control: internal
5072
+ foundation: finish_to_start
5073
+ reason: "metric measures and dimensions inherit the naming rule's cardinality discipline"
5074
+ source_ref: planner.telemetry.metric-cardinality
5075
+ strength: important
5076
+ target_ref: planner.acceptance.signal-telemetry-naming
5077
+ type: requires
5078
+ origin: "@afokapu/atdd-bun"
5079
+ - confidence: 1
5080
+ constraint: mandatory
5081
+ control: internal
5082
+ foundation: finish_to_start
5083
+ reason: "a source Telemetry: reference resolves against the tracking-plan registry"
5084
+ source_ref: coder.bun.telemetry-source-binding
5085
+ strength: important
5086
+ target_ref: planner.telemetry.tracking-plan-schema
5087
+ type: requires
5088
+ origin: "@afokapu/atdd-bun"
5089
+ - confidence: 1
5090
+ constraint: mandatory
5091
+ control: internal
5092
+ foundation: finish_to_start
5093
+ reason: only telemetry an acceptance has decided and the plan declares may be emitted by name
5094
+ source_ref: coder.bun.telemetry-raw-string-emit
5095
+ strength: important
5096
+ target_ref: planner.telemetry.acceptance-decision
5097
+ type: requires
5098
+ origin: "@afokapu/atdd-bun"
5099
+ - confidence: 1
5100
+ constraint: mandatory
5101
+ control: internal
5102
+ foundation: start_to_start
5103
+ reason: vendor SDKs stay in infrastructure adapters as framework imports stay out of domain
5104
+ source_ref: coder.bun.telemetry-vendor-sdk
5105
+ strength: important
5106
+ target_ref: coder.bun.commons-domain-no-framework-import
5107
+ type: runs_alongside
5108
+ origin: "@afokapu/atdd-bun"
5109
+ - confidence: 1
5110
+ constraint: mandatory
5111
+ control: internal
5112
+ foundation: finish_to_start
5113
+ reason: the forbidden-property list comes from the tracking-plan item
5114
+ source_ref: coder.bun.telemetry-forbidden-properties
5115
+ strength: important
5116
+ target_ref: planner.telemetry.tracking-plan-schema
5117
+ type: requires
5118
+ origin: "@afokapu/atdd-bun"
5119
+ - confidence: 1
5120
+ constraint: mandatory
5121
+ control: internal
5122
+ foundation: finish_to_start
5123
+ reason: the binding obligation runs on the same header the source rule resolves
5124
+ source_ref: coder.bun.telemetry-implementation-binding
5125
+ strength: important
5126
+ target_ref: coder.bun.telemetry-source-binding
5127
+ type: requires
5128
+ origin: "@afokapu/atdd-bun"
5129
+ - confidence: 1
5130
+ constraint: mandatory
5131
+ control: internal
5132
+ foundation: finish_to_start
5133
+ reason: required items come from acceptance decisions
5134
+ source_ref: coder.bun.telemetry-implementation-binding
5135
+ strength: important
5136
+ target_ref: planner.telemetry.acceptance-decision
5137
+ type: requires
5138
+ origin: "@afokapu/atdd-bun"
5139
+ - confidence: 1
5140
+ constraint: mandatory
5141
+ control: internal
5142
+ foundation: finish_to_start
5143
+ reason: a bound test must point at the acceptance whose decision requires the item
5144
+ source_ref: tester.bun.telemetry-test-binding
5145
+ strength: important
5146
+ target_ref: planner.telemetry.acceptance-decision
5147
+ type: requires
5148
+ origin: "@afokapu/atdd-bun"
5149
+ - confidence: 1
5150
+ constraint: mandatory
5151
+ control: internal
5152
+ foundation: finish_to_start
5153
+ reason: the captured-sink obligation carries the emission assertion into the telemetry profile
5154
+ source_ref: tester.bun.telemetry-captured-sink
5155
+ strength: important
5156
+ target_ref: tester.bun.telemetry-emit
5157
+ type: requires
5158
+ origin: "@afokapu/atdd-bun"
5159
+ - confidence: 1
5160
+ constraint: mandatory
5161
+ control: internal
5162
+ foundation: finish_to_start
5163
+ reason: exact identity extends the emission assertion from any-call to the named item
5164
+ source_ref: tester.bun.telemetry-identity-assertion
5165
+ strength: important
5166
+ target_ref: tester.bun.telemetry-emit
5167
+ type: requires
5168
+ origin: "@afokapu/atdd-bun"
5169
+ - confidence: 1
5170
+ constraint: mandatory
5171
+ control: internal
5172
+ foundation: finish_to_start
5173
+ reason: telemetry items follow the acceptance-has-test discipline
5174
+ source_ref: tester.bun.telemetry-required-item-coverage
5175
+ strength: important
5176
+ target_ref: traceability.plan.executable-acceptance-has-test
5177
+ type: requires
5178
+ origin: "@afokapu/atdd-bun"
5179
+ - confidence: 1
5180
+ constraint: mandatory
5181
+ control: internal
5182
+ foundation: finish_to_start
5183
+ reason: timing semantics are declared on the tracking-plan item
5184
+ source_ref: tester.bun.telemetry-timing-semantics
5185
+ strength: important
5186
+ target_ref: planner.telemetry.tracking-plan-schema
5187
+ type: requires
5188
+ origin: "@afokapu/atdd-bun"
package/src/enforce.ts CHANGED
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
4
4
  import { join, resolve } from "node:path";
5
5
  import { topologyFor } from "./topology";
6
6
 
7
- export type Profile = "traceability" | "topology" | "docs" | "planner" | "coder" | "tester" | "security" | "architecture" | "metrics" | "runtime" | "interlocking" | "htmx" | "design" | "all";
7
+ export type Profile = "traceability" | "topology" | "docs" | "planner" | "telemetry" | "coder" | "tester" | "security" | "architecture" | "metrics" | "runtime" | "interlocking" | "htmx" | "design" | "all";
8
8
 
9
9
  export type Violation = {
10
10
  rule_id: string;
@@ -27,6 +27,7 @@ const profiles: Record<Exclude<Profile, "all">, string[]> = {
27
27
  topology: ["atdd_topology"],
28
28
  docs: ["planner_docs_capability"],
29
29
  planner: ["planner_plan_integrity", "planner_schema_validation", "planner_static_validators", "atdd_topology"],
30
+ telemetry: ["planner_telemetry_plan", "bun_telemetry_code", "bun_telemetry_test"],
30
31
  coder: ["bun_green_traceability_detector", "bun_clean_architecture_detector", "bun_ts_metrics_detector", "bun_fullstack_detector", "bun_design_system_detector", "bun_responsive_detector", "atdd_topology"],
31
32
  tester: ["bun_tester_discipline_detector", "htmx_e2e_detector", "atdd_topology"],
32
33
  security: ["bun_security_hygiene_detector"],
package/src/index.ts CHANGED
@@ -5,6 +5,8 @@ export type { DocumentationCheck, DocumentationDeclaration, DocumentationRender,
5
5
  export { loadPlan, traceabilityPlan, validatePlan } from "./planner-kernel";
6
6
  export type { PlanArtifact, PlanFinding, PlanGraph, PlanKind } from "./planner-kernel";
7
7
  export { validateStaticPlannerConventions } from "./planner-validators";
8
+ export { validateTelemetryPlan, loadTelemetryFiles, telemetryDecisionOf, CONCRETE_URN } from "./telemetry-plan";
9
+ export type { TelemetryPlanItem, TelemetryFile } from "./telemetry-plan";
8
10
  export { PLANNER_SCHEMA_RULE_ID, validatePlannerSchemas } from "./planner-schema-validator";
9
11
  export { defaultHookPolicy, hookEvents, hooksStatus, installHooks, runHook, uninstallHooks } from "./hooks";
10
12
  export type { HookEvent, HookPolicy } from "./hooks";
package/src/integrity.ts CHANGED
@@ -2,7 +2,6 @@ import { existsSync } from "node:fs";
2
2
  import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { dirname, join, relative, resolve, sep } from "node:path";
4
4
  import { instructionPaths } from "./agent";
5
- import { concreteProfiles } from "./enforce";
6
5
  import { defaultHookPolicy, type HookPolicy } from "./hooks";
7
6
 
8
7
  /**
@@ -106,6 +105,10 @@ async function checkGenerated(root: string, packageRoot: string): Promise<Integr
106
105
  return findings;
107
106
  }
108
107
 
108
+ /** The operator's explicit profile list, or null when atdd-bun.yaml declares none. Deliberately not enabledProfiles():
109
+ * its absent-means-all default is right for execution and wrong for deciding whether a policy was ever declared. */
110
+ const explicitProfiles = (config: { profiles?: unknown }): string[] | null => Array.isArray(config.profiles) ? config.profiles.map(String) : null;
111
+
109
112
  /** Names of the policy fields in `current` that are looser than in `base`. */
110
113
  export function loosenedPolicy(base: Partial<HookPolicy> & { profiles?: unknown }, current: Partial<HookPolicy> & { profiles?: unknown }): string[] {
111
114
  const b = { ...defaultHookPolicy, ...base, worktrees: { ...defaultHookPolicy.worktrees, ...base.worktrees } }, c = { ...defaultHookPolicy, ...current, worktrees: { ...defaultHookPolicy.worktrees, ...current.worktrees } };
@@ -116,20 +119,33 @@ export function loosenedPolicy(base: Partial<HookPolicy> & { profiles?: unknown
116
119
  const removed = b.protected_branches.filter(x => !c.protected_branches.includes(x)), added = c.registry_paths.filter(x => !b.registry_paths.includes(x));
117
120
  if (removed.length) out.push(`protected_branches drops ${removed.join(", ")}`);
118
121
  if (added.length) out.push(`registry_paths adds ${added.join(", ")}`);
119
- // Deactivating a profile stops enforcing it; the operator may do it, as a change a human approves.
120
- const active = (config: { profiles?: unknown }): string[] => Array.isArray(config.profiles) ? config.profiles.map(String) : concreteProfiles;
121
- const dropped = active(base).filter(name => !active(current).includes(name));
122
+ // Profiles: an absent list runs every profile (enabledProfiles) but governs none. The first explicit list is the
123
+ // adoption that establishes the governed set, not a drop. From then on, dropping a profile or removing the list
124
+ // (explicit → implicit → narrower would otherwise be a two-step bypass) is loosening a human approves.
125
+ const before = explicitProfiles(base), after = explicitProfiles(current);
126
+ if (before && !after) out.push(`profiles becomes implicit: the explicit list [${before.join(", ")}] was removed`);
127
+ const dropped = before && after ? before.filter(name => !after.includes(name)) : [];
122
128
  if (dropped.length) out.push(`profiles drops ${dropped.join(", ")}`);
123
129
  return out;
124
130
  }
125
131
 
126
132
  /** atdd-bun.yaml is not looser than on the branch being merged into. */
127
133
  async function checkPolicy(root: string, base?: string, push = process.env.GITHUB_EVENT_NAME === "push"): Promise<IntegrityFinding[]> {
128
- const ref = base ?? process.env.ATDD_BASE_REF ?? (process.env.GITHUB_BASE_REF ? `origin/${process.env.GITHUB_BASE_REF}` : "origin/HEAD");
129
- if ((await git(root, ["rev-parse", "--verify", "--quiet", ref])).code) return [];
130
- let against = (await git(root, ["merge-base", "HEAD", ref])).out;
131
- // A CI push to the base branch has nothing to merge into: judge the pushed commit against its parent.
132
- if (push && against === (await git(root, ["rev-parse", "HEAD"])).out) against = (await git(root, ["rev-parse", "--verify", "--quiet", "HEAD~1"])).out;
134
+ // On a push, the generated CI passes the pre-push tip (github.event.before) as ATDD_BASE_REF, so a multi-commit push
135
+ // is judged as a whole: [docs, security] → no list → [docs] in one push cannot read as a first adoption.
136
+ const ref = base || process.env.ATDD_BASE_REF || (process.env.GITHUB_BASE_REF ? `origin/${process.env.GITHUB_BASE_REF}` : "origin/HEAD");
137
+ const newBranch = /^0+$/.test(ref), resolved = newBranch ? "" : (await git(root, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`])).out;
138
+ let against: string;
139
+ // An explicit baseline (the pre-push tip, the merge queue's target) that cannot be resolved fails closed.
140
+ if (!newBranch && !resolved && (base || process.env.ATDD_BASE_REF)) return [{ file: "atdd-bun.yaml", detail: `cannot resolve the policy baseline ${ref.slice(0, 7)} to judge this change against; fetch it (fetch-depth: 0)`, restore: "git fetch origin && re-run the check" }];
141
+ if (push) {
142
+ // A push is judged against the tip it replaced, directly, never a merge base: after a force push the merge base
143
+ // can predate the policy being removed. A new branch has no previous tip and is judged against its parent.
144
+ against = resolved && resolved !== (await git(root, ["rev-parse", "HEAD"])).out ? resolved : (await git(root, ["rev-parse", "--verify", "--quiet", "HEAD~1"])).out;
145
+ } else {
146
+ if (!resolved) return [];
147
+ against = (await git(root, ["merge-base", "HEAD", ref])).out;
148
+ }
133
149
  if (!against) return [];
134
150
  const read = async (text: string | null) => (text ? Bun.YAML.parse(text) ?? {} : {}) as Partial<HookPolicy>;
135
151
  const before = await git(root, ["show", `${against}:atdd-bun.yaml`]), path = join(root, "atdd-bun.yaml");
package/src/setup.ts CHANGED
@@ -2,6 +2,21 @@ import { ciInit, ciStatus } from "./ci";
2
2
  import { hooksStatus, installHooks } from "./hooks";
3
3
  import { agentInit, agentStatus } from "./agent";
4
4
  import { integrityInit, integrityStatus } from "./integrity";
5
+ import { concreteProfiles } from "./enforce";
6
+ import { existsSync } from "node:fs";
7
+ import { writeFile } from "node:fs/promises";
8
+ import { join } from "node:path";
9
+
10
+ /** A new atdd-bun.yaml declares every profile explicitly. An absent list runs every profile but governs none, so
11
+ * without this a greenfield repository's first `profiles:` line could switch most of them off unreported. A
12
+ * brownfield repository trims the list before its first commit; after that, dropping one is a reported loosening.
13
+ * An existing atdd-bun.yaml is never touched. */
14
+ export async function policyInit(root = process.cwd()) {
15
+ const file = join(root, "atdd-bun.yaml");
16
+ if (existsSync(file)) return { ok: true, message: `${file} kept` };
17
+ await writeFile(file, `# Generated by atdd-bun init. The profiles this repository enforces; trim the list before the first commit to\n# adopt gradually. Once committed, removing a profile or the list is reported by the integrity check.\nprofiles: [${concreteProfiles.join(", ")}]\n`);
18
+ return { ok: true, message: file };
19
+ }
5
20
 
6
21
  /** Install the package's opt-in local surfaces without touching unrelated
7
22
  * workflows or hook paths. Dependency installation itself never calls this. */
@@ -21,5 +36,6 @@ export async function initializeRepository(root = process.cwd(), replace = false
21
36
  const existingIntegrity = await integrityStatus(root);
22
37
  const integrity = replace || !existingIntegrity.ok ? await integrityInit(root, replace) : existingIntegrity;
23
38
  if (!integrity.ok) return { ok: false, message: `integrity test: ${integrity.message}` };
24
- return { ok: true, message: `hooks: ${hooks.message}\nCI workflow: ${ci.message}\nagent skill: ${agent.message}\nintegrity test: ${integrity.message}` };
39
+ const policy = await policyInit(root);
40
+ return { ok: true, message: `hooks: ${hooks.message}\nCI workflow: ${ci.message}\nagent skill: ${agent.message}\nintegrity test: ${integrity.message}\npolicy: ${policy.message}` };
25
41
  }