@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.
- package/README.md +13 -3
- package/conventions/coder.bun/coder.bun.telemetry-forbidden-properties.convention.yaml +51 -0
- package/conventions/coder.bun/coder.bun.telemetry-implementation-binding.convention.yaml +45 -0
- package/conventions/coder.bun/coder.bun.telemetry-raw-string-emit.convention.yaml +53 -0
- package/conventions/coder.bun/coder.bun.telemetry-source-binding.convention.yaml +48 -0
- package/conventions/coder.bun/coder.bun.telemetry-vendor-sdk.convention.yaml +51 -0
- package/conventions/planner.telemetry/planner.telemetry.acceptance-decision.convention.yaml +58 -0
- package/conventions/planner.telemetry/planner.telemetry.logical-ownership.convention.yaml +50 -0
- package/conventions/planner.telemetry/planner.telemetry.metric-cardinality.convention.yaml +47 -0
- package/conventions/planner.telemetry/planner.telemetry.tracking-plan-schema.convention.yaml +75 -0
- package/conventions/tester.bun/tester.bun.telemetry-captured-sink.convention.yaml +47 -0
- package/conventions/tester.bun/tester.bun.telemetry-identity-assertion.convention.yaml +44 -0
- package/conventions/tester.bun/tester.bun.telemetry-required-item-coverage.convention.yaml +44 -0
- package/conventions/tester.bun/tester.bun.telemetry-test-binding.convention.yaml +52 -0
- package/conventions/tester.bun/tester.bun.telemetry-timing-semantics.convention.yaml +53 -0
- package/detectors/bun_telemetry_code/atdd.implementation.yaml +24 -0
- package/detectors/bun_telemetry_code/calls.mjs +104 -0
- package/detectors/bun_telemetry_code/checks/t_forbidden_properties.mjs +46 -0
- package/detectors/bun_telemetry_code/checks/t_implementation_binding.mjs +41 -0
- package/detectors/bun_telemetry_code/checks/t_raw_string_emit.mjs +34 -0
- package/detectors/bun_telemetry_code/checks/t_source_binding.mjs +48 -0
- package/detectors/bun_telemetry_code/checks/t_vendor_sdk.mjs +38 -0
- package/detectors/bun_telemetry_code/detect.mjs +50 -0
- package/detectors/bun_telemetry_code/fixtures/clean/plan/commons/E001.yaml +10 -0
- package/detectors/bun_telemetry_code/fixtures/clean/plan/commons/E002.yaml +7 -0
- package/detectors/bun_telemetry_code/fixtures/clean/plan/commons/_commons.yaml +6 -0
- package/detectors/bun_telemetry_code/fixtures/clean/src/wagons/commons/features/ingress/domain/accept-response.ts +8 -0
- package/detectors/bun_telemetry_code/fixtures/clean/src/wagons/commons/features/ingress/infrastructure/otel-adapter.ts +8 -0
- package/detectors/bun_telemetry_code/fixtures/clean/telemetry/commons/response-invocation-accepted/event.be.json +16 -0
- package/detectors/bun_telemetry_code/fixtures/clean/telemetry/commons/response-invocation-accepted/metric.be.duration.json +19 -0
- package/detectors/bun_telemetry_code/fixtures/clean/tests/wagons/commons/features/ingress/unit/accept-response.telemetry.test.ts +13 -0
- package/detectors/bun_telemetry_code/fixtures/clean/tests/wagons/commons/features/ingress/unit/accept-response.test.ts +9 -0
- package/detectors/bun_telemetry_code/fixtures/dirty/plan/commons/E001.yaml +8 -0
- package/detectors/bun_telemetry_code/fixtures/dirty/plan/commons/_commons.yaml +6 -0
- package/detectors/bun_telemetry_code/fixtures/dirty/src/wagons/commons/features/ingress/domain/accept-response.ts +13 -0
- package/detectors/bun_telemetry_code/fixtures/dirty/src/wagons/commons/features/ingress/domain/tracing.ts +3 -0
- package/detectors/bun_telemetry_code/fixtures/dirty/telemetry/commons/response-invocation-accepted/event.be.json +16 -0
- package/detectors/bun_telemetry_code/fixtures/dirty/tests/wagons/commons/features/ingress/unit/probe.test.ts +7 -0
- package/detectors/bun_telemetry_code/registry.mjs +34 -0
- package/detectors/bun_telemetry_test/_shared.mjs +92 -0
- package/detectors/bun_telemetry_test/atdd.implementation.yaml +24 -0
- package/detectors/bun_telemetry_test/checks/t_captured_sink.mjs +35 -0
- package/detectors/bun_telemetry_test/checks/t_identity_assertion.mjs +45 -0
- package/detectors/bun_telemetry_test/checks/t_required_item_coverage.mjs +37 -0
- package/detectors/bun_telemetry_test/checks/t_test_binding.mjs +56 -0
- package/detectors/bun_telemetry_test/checks/t_timing_semantics.mjs +51 -0
- package/detectors/bun_telemetry_test/detect.mjs +50 -0
- package/detectors/bun_telemetry_test/fixtures/clean/plan/commons/E001.yaml +10 -0
- package/detectors/bun_telemetry_test/fixtures/clean/plan/commons/E002.yaml +7 -0
- package/detectors/bun_telemetry_test/fixtures/clean/plan/commons/_commons.yaml +6 -0
- package/detectors/bun_telemetry_test/fixtures/clean/src/wagons/commons/features/ingress/domain/accept-response.ts +8 -0
- package/detectors/bun_telemetry_test/fixtures/clean/telemetry/commons/response-invocation-accepted/event.be.json +16 -0
- package/detectors/bun_telemetry_test/fixtures/clean/telemetry/commons/response-invocation-accepted/metric.be.duration.json +19 -0
- package/detectors/bun_telemetry_test/fixtures/clean/tests/wagons/commons/features/ingress/unit/accept-response.telemetry.test.ts +13 -0
- package/detectors/bun_telemetry_test/fixtures/clean/tests/wagons/commons/features/ingress/unit/accept-response.test.ts +7 -0
- package/detectors/bun_telemetry_test/fixtures/dirty/plan/commons/E001.yaml +10 -0
- package/detectors/bun_telemetry_test/fixtures/dirty/plan/commons/E002.yaml +7 -0
- package/detectors/bun_telemetry_test/fixtures/dirty/plan/commons/_commons.yaml +6 -0
- package/detectors/bun_telemetry_test/fixtures/dirty/src/wagons/commons/features/ingress/domain/accept-response.ts +8 -0
- package/detectors/bun_telemetry_test/fixtures/dirty/telemetry/commons/response-invocation-accepted/event.be.json +16 -0
- package/detectors/bun_telemetry_test/fixtures/dirty/telemetry/commons/response-invocation-accepted/metric.be.duration.json +19 -0
- package/detectors/bun_telemetry_test/fixtures/dirty/tests/wagons/commons/features/ingress/unit/dangling.telemetry.test.ts +9 -0
- package/detectors/bun_telemetry_test/fixtures/dirty/tests/wagons/commons/features/ingress/unit/loose.telemetry.test.ts +11 -0
- package/detectors/bun_telemetry_test/fixtures/dirty/tests/wagons/commons/features/ingress/unit/unbound.telemetry.test.ts +10 -0
- package/detectors/planner_telemetry_plan/atdd.implementation.yaml +22 -0
- package/detectors/planner_telemetry_plan/detect.mjs +9 -0
- package/detectors/planner_telemetry_plan/fixtures/clean/plan/commons/E001.yaml +10 -0
- package/detectors/planner_telemetry_plan/fixtures/clean/plan/commons/E002.yaml +7 -0
- package/detectors/planner_telemetry_plan/fixtures/clean/plan/commons/_commons.yaml +6 -0
- package/detectors/planner_telemetry_plan/fixtures/clean/src/wagons/commons/features/ingress/domain/accept-response.ts +8 -0
- package/detectors/planner_telemetry_plan/fixtures/clean/telemetry/commons/response-invocation-accepted/event.be.json +16 -0
- package/detectors/planner_telemetry_plan/fixtures/clean/telemetry/commons/response-invocation-accepted/metric.be.duration.json +19 -0
- package/detectors/planner_telemetry_plan/fixtures/clean/tests/wagons/commons/features/ingress/unit/accept-response.telemetry.test.ts +13 -0
- package/detectors/planner_telemetry_plan/fixtures/dirty/plan/commons/E001.yaml +9 -0
- package/detectors/planner_telemetry_plan/fixtures/dirty/plan/commons/E002.yaml +4 -0
- package/detectors/planner_telemetry_plan/fixtures/dirty/plan/commons/E003.yaml +7 -0
- package/detectors/planner_telemetry_plan/fixtures/dirty/plan/commons/_commons.yaml +6 -0
- package/detectors/planner_telemetry_plan/fixtures/dirty/telemetry/commons/extra.json +1 -0
- package/detectors/planner_telemetry_plan/fixtures/dirty/telemetry/commons/orphan-artifact/event.be.json +14 -0
- package/detectors/planner_telemetry_plan/fixtures/dirty/telemetry/commons/response-invocation-accepted/event.be.json +16 -0
- package/detectors/planner_telemetry_plan/fixtures/dirty/telemetry/commons/response-invocation-accepted/metric.be.duration.json +22 -0
- package/detectors/planner_telemetry_plan/fixtures/dirty/telemetry/commons/response-invocation-accepted/notes.json +1 -0
- package/integrity.json +94 -11
- package/lib/scan.mjs +37 -0
- package/package.json +1 -1
- package/planner-schemas/acceptance.schema.json +70 -1
- package/planner-schemas/telemetry-plan.schema.json +133 -0
- package/relationships.yaml +174 -0
- package/src/enforce.ts +2 -1
- package/src/index.ts +2 -0
- package/src/integrity.ts +25 -9
- package/src/setup.ts +17 -1
- package/src/telemetry-plan.ts +223 -0
- package/src/topology.ts +3 -1
- package/templates/github/atdd-bun.yml +5 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
schema_version: 1.1.0
|
|
2
|
+
rule_id: tester.bun.telemetry-test-binding
|
|
3
|
+
kind: rule
|
|
4
|
+
status: active
|
|
5
|
+
name: Telemetry tests bind the acceptance and the item they prove
|
|
6
|
+
statement: >-
|
|
7
|
+
A `// Telemetry:` reference in a test file is well-formed, resolves to a tracking-plan item, and
|
|
8
|
+
the test carries an `Acceptance:` binding to a declared acceptance whose telemetry decision
|
|
9
|
+
requires that item (REQUIRED).
|
|
10
|
+
terms:
|
|
11
|
+
- term_id: telemetry_test
|
|
12
|
+
text: >-
|
|
13
|
+
a test file with a Telemetry: header, a TELEMETRY/EVENT/METRIC URN kind segment, or
|
|
14
|
+
*.telemetry.test.* colocation.
|
|
15
|
+
- term_id: bound_acceptance
|
|
16
|
+
text: >-
|
|
17
|
+
the acceptance named by the test's Acceptance: header. Its telemetry decision must list the
|
|
18
|
+
bound item — a test proves a requirement, not an acquaintance.
|
|
19
|
+
content:
|
|
20
|
+
summary: >-
|
|
21
|
+
The @covers edge of the telemetry graph: acceptance → item → test. A telemetry test that binds
|
|
22
|
+
an item but no acceptance proves something nobody required; one that binds an acceptance whose
|
|
23
|
+
decision does not list the item proves a relationship the plan never declared.
|
|
24
|
+
normative_text: |
|
|
25
|
+
Every Telemetry: header in a test file is judged on its own line: concrete-URN grammar,
|
|
26
|
+
registry resolution, presence of Acceptance: bindings, the bindings' existence in the plan,
|
|
27
|
+
and at least ONE bound acceptance's decision actually requiring the bound item. A spec file
|
|
28
|
+
legitimately covers many acceptances (the two-level @covers model); the requiring one may be
|
|
29
|
+
any of them, so the judgement is over the whole set — none requiring is the finding. A
|
|
30
|
+
header inside a string literal is not a header.
|
|
31
|
+
fix_hint: |
|
|
32
|
+
Bind both ends of the proof:
|
|
33
|
+
|
|
34
|
+
// URN: test:commons:ingress:E001-UNIT-001
|
|
35
|
+
// Acceptance: acc:commons:E001-UNIT-001
|
|
36
|
+
// Telemetry: telemetry:event:be:commons:response-invocation-accepted
|
|
37
|
+
exceptions:
|
|
38
|
+
- >-
|
|
39
|
+
Tests identified as telemetry tests by URN segment or colocation alone (no Telemetry: header)
|
|
40
|
+
are judged by the captured-sink and identity rules, not this one.
|
|
41
|
+
- >-
|
|
42
|
+
Inert until adoption, like every rule in this family.
|
|
43
|
+
metadata:
|
|
44
|
+
aliases:
|
|
45
|
+
- TESTER-TELEMETRY-TEST-BINDING-001
|
|
46
|
+
severity: 2
|
|
47
|
+
# strict: atdd-bun fails on every finding; it has no advisory mode and no ratchet baseline.
|
|
48
|
+
disposition: strict
|
|
49
|
+
introduced_in: 0.7.0
|
|
50
|
+
implementation:
|
|
51
|
+
type: validator
|
|
52
|
+
ref: bun_telemetry_test
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
schema_version: 1.1.0
|
|
2
|
+
rule_id: tester.bun.telemetry-timing-semantics
|
|
3
|
+
kind: rule
|
|
4
|
+
status: active
|
|
5
|
+
name: Declared timing semantics are exercised by a bound test
|
|
6
|
+
statement: >-
|
|
7
|
+
Every timing semantic a tracking-plan item declares (post-commit, absent-after-rollback,
|
|
8
|
+
correlation-across-async) is exercised by at least one telemetry test bound to that item
|
|
9
|
+
(REQUIRED).
|
|
10
|
+
terms:
|
|
11
|
+
- term_id: timing_semantic
|
|
12
|
+
text: >-
|
|
13
|
+
a declared critical ordering property of an emission: post-commit (only after the transaction
|
|
14
|
+
commits), absent-after-rollback (never when it rolls back), correlation-across-async
|
|
15
|
+
(correlation context continues across an async handoff).
|
|
16
|
+
- term_id: exercised
|
|
17
|
+
text: >-
|
|
18
|
+
the bound test references the behaviour — commit, rollback, correlation/context — in what it
|
|
19
|
+
asserts or names. The check is plan-driven and lexical by design: it requires the plan to say
|
|
20
|
+
when ordering matters and the test to speak to it.
|
|
21
|
+
content:
|
|
22
|
+
summary: >-
|
|
23
|
+
Transaction-time telemetry bugs (the event that fires before the commit it describes, the
|
|
24
|
+
rollback that still emits, the context lost across an await) are exactly the ones return-value
|
|
25
|
+
tests never catch. The plan declares when ordering matters; the bound tests must speak to it.
|
|
26
|
+
normative_text: |
|
|
27
|
+
Timing semantics live on the tracking-plan item's timing list. For each declared semantic, at
|
|
28
|
+
least one test bound to the item by a Telemetry: header must reference the corresponding
|
|
29
|
+
behaviour — in code or in a test title; comments do not satisfy, and neither does prose in
|
|
30
|
+
the tracking plan. This deliberately does not prove ordering statically — it cannot, and
|
|
31
|
+
pretending otherwise would be false precision. Items that declare no timing are not judged,
|
|
32
|
+
which is how diagnostic logs and internal spans stay out of scope.
|
|
33
|
+
fix_hint: |
|
|
34
|
+
Declare on the item, exercise in the bound test:
|
|
35
|
+
|
|
36
|
+
"timing": ["post-commit"] // the plan says ordering matters
|
|
37
|
+
test("emits the planned event once the invocation commits", () => { ... }); // the test speaks to it
|
|
38
|
+
exceptions:
|
|
39
|
+
- >-
|
|
40
|
+
No declared timing, no obligation: a diagnostic log with an empty timing list never triggers
|
|
41
|
+
this rule.
|
|
42
|
+
- >-
|
|
43
|
+
Inert until adoption, like every rule in this family.
|
|
44
|
+
metadata:
|
|
45
|
+
aliases:
|
|
46
|
+
- TESTER-TELEMETRY-TIMING-SEMANTICS-001
|
|
47
|
+
severity: 3
|
|
48
|
+
# strict: atdd-bun fails on every finding; it has no advisory mode and no ratchet baseline.
|
|
49
|
+
disposition: strict
|
|
50
|
+
introduced_in: 0.7.0
|
|
51
|
+
implementation:
|
|
52
|
+
type: validator
|
|
53
|
+
ref: bun_telemetry_test
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
schema_version: "1.0.0"
|
|
2
|
+
kind: implementation
|
|
3
|
+
subtype: validator
|
|
4
|
+
implementation_id: coder.telemetry.impl
|
|
5
|
+
targets_workspace: atdd.workspace.bun
|
|
6
|
+
contract_version: "1.0.0"
|
|
7
|
+
# The code-side half of the telemetry profile: source references into the tracking
|
|
8
|
+
# plan, raw-string and forbidden-property emission, and the vendor-SDK boundary
|
|
9
|
+
# around the neutral TelemetryPort. Inert until the repository adopts the
|
|
10
|
+
# capability (telemetry root present, or any acceptance declaring a decision).
|
|
11
|
+
emits_rule_ids:
|
|
12
|
+
- coder.bun.telemetry-source-binding
|
|
13
|
+
- coder.bun.telemetry-raw-string-emit
|
|
14
|
+
- coder.bun.telemetry-vendor-sdk
|
|
15
|
+
- coder.bun.telemetry-forbidden-properties
|
|
16
|
+
- coder.bun.telemetry-implementation-binding
|
|
17
|
+
realizes_convention:
|
|
18
|
+
- coder.bun.telemetry-source-binding
|
|
19
|
+
- coder.bun.telemetry-raw-string-emit
|
|
20
|
+
- coder.bun.telemetry-vendor-sdk
|
|
21
|
+
- coder.bun.telemetry-forbidden-properties
|
|
22
|
+
- coder.bun.telemetry-implementation-binding
|
|
23
|
+
entrypoint: detect.mjs
|
|
24
|
+
report: detect.mjs
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// calls.mjs — emit-call extraction shared by the raw-string and forbidden-property
|
|
2
|
+
// checks. Never spawned as a check (the family runner only spawns checks/*.mjs).
|
|
3
|
+
//
|
|
4
|
+
// An emit-like call is `emit`, `emitted`, `track`, `capture`, `record`, `logEvent`,
|
|
5
|
+
// `emitEvent` or `sendEvent` invoked as a function or method with a STRING LITERAL
|
|
6
|
+
// as its first argument. The lookbehind rejects only word-character prefixes, so
|
|
7
|
+
// `telemetry.emit(...)` matches while `resubmit(...)` does not. Calls whose first
|
|
8
|
+
// argument is computed (a variable, a template literal, a contract constant) are
|
|
9
|
+
// invisible to these checks by design: they are what the generated contracts use.
|
|
10
|
+
|
|
11
|
+
// Comment masking (strings kept) lives in lib/scan.mjs; re-exported for the member checks.
|
|
12
|
+
export { maskComments } from "../../lib/scan.mjs";
|
|
13
|
+
|
|
14
|
+
const EMIT_CALLEE = /(?<![A-Za-z0-9_$])(?:emit|emitted|track|capture|record|logEvent|emitEvent|sendEvent)(?=\s*\()/g;
|
|
15
|
+
|
|
16
|
+
/** Every emit-like call with a string-literal first argument: {index, value, argEnd}. */
|
|
17
|
+
export function emitStringCalls(text) {
|
|
18
|
+
const calls = [];
|
|
19
|
+
for (const callee of text.matchAll(EMIT_CALLEE)) {
|
|
20
|
+
let i = callee.index + callee[0].length;
|
|
21
|
+
while (i < text.length && /\s/.test(text[i])) i++;
|
|
22
|
+
if (text[i] !== "(") continue;
|
|
23
|
+
i++;
|
|
24
|
+
while (i < text.length && /\s/.test(text[i])) i++;
|
|
25
|
+
const quote = text[i];
|
|
26
|
+
if (quote !== '"' && quote !== "'") continue;
|
|
27
|
+
let j = i + 1;
|
|
28
|
+
while (j < text.length && text[j] !== quote && text[j] !== "\n") {
|
|
29
|
+
if (text[j] === "\\") j++;
|
|
30
|
+
j++;
|
|
31
|
+
}
|
|
32
|
+
if (text[j] !== quote) continue;
|
|
33
|
+
calls.push({ index: callee.index, value: text.slice(i + 1, j), argEnd: j + 1 });
|
|
34
|
+
}
|
|
35
|
+
return calls;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Does a string look like a telemetry event NAME (as opposed to a log level or a key)? */
|
|
39
|
+
export function looksLikeEventName(value) {
|
|
40
|
+
if (value.startsWith("telemetry:")) return true;
|
|
41
|
+
if (/^[a-z]+(?:_[a-z0-9]+)+$/.test(value)) return true; // order_accepted
|
|
42
|
+
if (/^[a-z][a-zA-Z0-9]*$/.test(value) && /[a-z][A-Z]/.test(value)) return true; // orderCreated
|
|
43
|
+
if (/^[A-Z][A-Za-z0-9]*(?: [A-Za-z0-9]+)+$/.test(value)) return true; // Order Created
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The object literal starting at text[from] (already positioned at `{`), or null. */
|
|
48
|
+
export function objectLiteralAt(text, from) {
|
|
49
|
+
if (text[from] !== "{") return null;
|
|
50
|
+
let depth = 0;
|
|
51
|
+
for (let i = from; i < text.length; i++) {
|
|
52
|
+
const ch = text[i];
|
|
53
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
54
|
+
let j = i + 1;
|
|
55
|
+
while (j < text.length && text[j] !== ch && text[j] !== "\n") {
|
|
56
|
+
if (text[j] === "\\") j++;
|
|
57
|
+
j++;
|
|
58
|
+
}
|
|
59
|
+
i = text[j] === ch ? j : j - 1;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (ch === "{") depth++;
|
|
63
|
+
else if (ch === "}") {
|
|
64
|
+
depth--;
|
|
65
|
+
if (depth === 0) return text.slice(from, i + 1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Top-level property names of an object-literal text (braces included). Shorthand
|
|
72
|
+
* properties carry no name of their own and are invisible to this scan. */
|
|
73
|
+
export function topLevelKeys(objectText) {
|
|
74
|
+
const keys = [];
|
|
75
|
+
let depth = 0, i = 0, atKeyStart = true;
|
|
76
|
+
while (i < objectText.length) {
|
|
77
|
+
const ch = objectText[i];
|
|
78
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
79
|
+
let j = i + 1;
|
|
80
|
+
while (j < objectText.length && objectText[j] !== ch && objectText[j] !== "\n") {
|
|
81
|
+
if (objectText[j] === "\\") j++;
|
|
82
|
+
j++;
|
|
83
|
+
}
|
|
84
|
+
i = j < objectText.length ? j + 1 : j;
|
|
85
|
+
atKeyStart = false;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (ch === "{" || ch === "[" || ch === "(") { depth++; i++; atKeyStart = ch === "{" || ch === ","; continue; }
|
|
89
|
+
if (ch === "}" || ch === "]" || ch === ")") { depth--; i++; atKeyStart = false; continue; }
|
|
90
|
+
if (depth === 1 && atKeyStart) {
|
|
91
|
+
const match = /^([A-Za-z_$][\w$]*)\s*:/.exec(objectText.slice(i));
|
|
92
|
+
if (match) {
|
|
93
|
+
keys.push(match[1]);
|
|
94
|
+
i += match[0].length;
|
|
95
|
+
atKeyStart = false;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (ch === ",") { atKeyStart = true; i++; continue; }
|
|
100
|
+
if (!/[\s:]/.test(ch)) atKeyStart = false;
|
|
101
|
+
i++;
|
|
102
|
+
}
|
|
103
|
+
return keys;
|
|
104
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Member check: coder.bun.telemetry-forbidden-properties (telemetry code family)
|
|
3
|
+
//
|
|
4
|
+
// CONTRACT (v1.1): reads ATDD_SCAN_ROOTS / ATDD_SCAN_EXCLUDES, writes RAW violations
|
|
5
|
+
// to ATDD_VIOLATIONS_REPORT, exits 0 regardless of count.
|
|
6
|
+
import { walk, readRoots, readExcludes, readText, emit, locate, SOURCE_EXT } from "../../../lib/scan.mjs";
|
|
7
|
+
import { registry, normalizeName } from "../registry.mjs";
|
|
8
|
+
import { maskComments, emitStringCalls, objectLiteralAt, topLevelKeys } from "../calls.mjs";
|
|
9
|
+
|
|
10
|
+
// The tracking plan's forbidden_properties list (raw payloads, secrets, prompts) is a
|
|
11
|
+
// promise about what NEVER leaves the process on that item. When an emit-like call
|
|
12
|
+
// names a declared item, the object literal it passes is checked against that list —
|
|
13
|
+
// snake_case in the plan meets camelCase in code through normalization.
|
|
14
|
+
const RULE = "coder.bun.telemetry-forbidden-properties";
|
|
15
|
+
|
|
16
|
+
const { adopted, forbidden } = await registry();
|
|
17
|
+
const violations = [];
|
|
18
|
+
if (adopted) {
|
|
19
|
+
const excludes = readExcludes();
|
|
20
|
+
for (const root of readRoots()) {
|
|
21
|
+
for (const file of walk(root, excludes, SOURCE_EXT)) {
|
|
22
|
+
const text = readText(file);
|
|
23
|
+
if (!text) continue;
|
|
24
|
+
const masked = maskComments(text);
|
|
25
|
+
for (const call of emitStringCalls(masked)) {
|
|
26
|
+
const banned = forbidden.get(call.value);
|
|
27
|
+
if (!banned) continue;
|
|
28
|
+
// From the end of the first argument, skip the separator and read the properties object.
|
|
29
|
+
let i = call.argEnd;
|
|
30
|
+
while (i < masked.length && /\s/.test(masked[i])) i++;
|
|
31
|
+
if (masked[i] !== ",") continue;
|
|
32
|
+
i++;
|
|
33
|
+
while (i < masked.length && /\s/.test(masked[i])) i++;
|
|
34
|
+
const objectText = objectLiteralAt(masked, i);
|
|
35
|
+
if (!objectText) continue;
|
|
36
|
+
for (const key of topLevelKeys(objectText)) {
|
|
37
|
+
if (!banned.has(normalizeName(key))) continue;
|
|
38
|
+
violations.push({ rule_id: RULE, file, ...locate(text, call.index),
|
|
39
|
+
evidence: `passes forbidden property '${key}' to ${call.value}; the tracking plan forbids it on this item` });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
process.stderr.write(`bun-telemetry[forbidden-properties]: ${violations.length} violation(s)\n`);
|
|
46
|
+
emit(violations);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Member check: coder.bun.telemetry-implementation-binding (telemetry code family)
|
|
3
|
+
//
|
|
4
|
+
// CONTRACT (v1.1): reads ATDD_SCAN_ROOTS / ATDD_SCAN_EXCLUDES, writes RAW violations
|
|
5
|
+
// to ATDD_VIOLATIONS_REPORT, exits 0 regardless of count.
|
|
6
|
+
import { walk, readRoots, readExcludes, readText, emit, SOURCE_EXT } from "../../../lib/scan.mjs";
|
|
7
|
+
import { registry } from "../registry.mjs";
|
|
8
|
+
|
|
9
|
+
// The third edge of the traceability graph: every telemetry item an acceptance
|
|
10
|
+
// REQUIRES must be bound to the implementation that emits it, by a Telemetry:
|
|
11
|
+
// header in non-test source. A required item no source claims is planned telemetry
|
|
12
|
+
// nobody emits — the plan and the code have diverged, and this is where it shows.
|
|
13
|
+
const RULE = "coder.bun.telemetry-implementation-binding";
|
|
14
|
+
const TELEMETRY_HEADER = /^[ \t]*\/\/[ \t]*Telemetry:[ \t]*(\S+)[ \t]*$/;
|
|
15
|
+
const STRING_ON_LINE = /(["'`])(?:\\.|(?!\1)[^\n\\])*\1/g;
|
|
16
|
+
|
|
17
|
+
const { adopted, ids, requiredIds, itemFileById } = await registry();
|
|
18
|
+
const violations = [];
|
|
19
|
+
if (adopted) {
|
|
20
|
+
const bound = new Set();
|
|
21
|
+
const excludes = readExcludes();
|
|
22
|
+
for (const root of readRoots()) {
|
|
23
|
+
for (const file of walk(root, excludes, SOURCE_EXT)) {
|
|
24
|
+
const text = readText(file);
|
|
25
|
+
if (!text) continue;
|
|
26
|
+
for (const raw of text.split("\n")) {
|
|
27
|
+
const match = TELEMETRY_HEADER.exec(raw.replace(STRING_ON_LINE, '""'));
|
|
28
|
+
if (match && ids.has(match[1])) bound.add(match[1]);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (const id of [...requiredIds].sort()) {
|
|
33
|
+
// An unresolvable required URN is the acceptance-decision rule's finding; judge only declared items.
|
|
34
|
+
if (!ids.has(id) || bound.has(id)) continue;
|
|
35
|
+
violations.push({ rule_id: RULE, file: itemFileById.get(id) ?? "telemetry/", line: 1, col: 1,
|
|
36
|
+
evidence: `${id} is required by an acceptance but no implementation source binds it with a // Telemetry: header`,
|
|
37
|
+
source_line: "" });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
process.stderr.write(`bun-telemetry[implementation-binding]: ${violations.length} violation(s)\n`);
|
|
41
|
+
emit(violations);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Member check: coder.bun.telemetry-raw-string-emit (telemetry code family)
|
|
3
|
+
//
|
|
4
|
+
// CONTRACT (v1.1): reads ATDD_SCAN_ROOTS / ATDD_SCAN_EXCLUDES, writes RAW violations
|
|
5
|
+
// to ATDD_VIOLATIONS_REPORT, exits 0 regardless of count.
|
|
6
|
+
import { walk, readRoots, readExcludes, readText, emit, locate, SOURCE_EXT } from "../../../lib/scan.mjs";
|
|
7
|
+
import { registry } from "../registry.mjs";
|
|
8
|
+
import { maskComments, emitStringCalls, looksLikeEventName } from "../calls.mjs";
|
|
9
|
+
|
|
10
|
+
// An emit-like call whose first argument is a raw string that names an event must name
|
|
11
|
+
// a DECLARED event. Undeclared names are how tracking plans rot: the string compiles,
|
|
12
|
+
// the dashboard never fills. A declared raw id passes for now; once generated contracts
|
|
13
|
+
// exist, emitting even a declared id by string is drift the contract exists to prevent.
|
|
14
|
+
const RULE = "coder.bun.telemetry-raw-string-emit";
|
|
15
|
+
|
|
16
|
+
const { adopted, ids } = await registry();
|
|
17
|
+
const violations = [];
|
|
18
|
+
if (adopted) {
|
|
19
|
+
const excludes = readExcludes();
|
|
20
|
+
for (const root of readRoots()) {
|
|
21
|
+
for (const file of walk(root, excludes, SOURCE_EXT)) {
|
|
22
|
+
const text = readText(file);
|
|
23
|
+
if (!text) continue;
|
|
24
|
+
const masked = maskComments(text);
|
|
25
|
+
for (const call of emitStringCalls(masked)) {
|
|
26
|
+
if (!looksLikeEventName(call.value) || ids.has(call.value)) continue;
|
|
27
|
+
violations.push({ rule_id: RULE, file, ...locate(text, call.index),
|
|
28
|
+
evidence: `undeclared raw-string telemetry name '${call.value}'; declare it in the tracking plan (telemetry/<theme>/<artifact>/) and emit it through the generated contract` });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
process.stderr.write(`bun-telemetry[raw-string-emit]: ${violations.length} violation(s)\n`);
|
|
34
|
+
emit(violations);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Member check: coder.bun.telemetry-source-binding (telemetry code family)
|
|
3
|
+
//
|
|
4
|
+
// CONTRACT (v1.1): reads ATDD_SCAN_ROOTS / ATDD_SCAN_EXCLUDES, writes RAW violations
|
|
5
|
+
// to ATDD_VIOLATIONS_REPORT, exits 0 regardless of count.
|
|
6
|
+
import { walk, readRoots, readExcludes, readText, emit, SOURCE_EXT } from "../../../lib/scan.mjs";
|
|
7
|
+
import { registry } from "../registry.mjs";
|
|
8
|
+
|
|
9
|
+
// A source file binds itself to the tracking plan with a Telemetry: header naming the
|
|
10
|
+
// concrete item it emits. This is the source-to-telemetry traceability edge: every URN
|
|
11
|
+
// so written must be a well-formed concrete URN that resolves to a registry entry.
|
|
12
|
+
const RULE = "coder.bun.telemetry-source-binding";
|
|
13
|
+
const TELEMETRY_HEADER = /^[ \t]*\/\/[ \t]*Telemetry:[ \t]*(\S+)[ \t]*$/;
|
|
14
|
+
const STRING_ON_LINE = /(["'`])(?:\\.|(?!\1)[^\n\\])*\1/g;
|
|
15
|
+
|
|
16
|
+
const { adopted, ids, concreteUrn } = await registry();
|
|
17
|
+
const violations = [];
|
|
18
|
+
if (adopted) {
|
|
19
|
+
const excludes = readExcludes();
|
|
20
|
+
for (const root of readRoots()) {
|
|
21
|
+
for (const file of walk(root, excludes, SOURCE_EXT)) {
|
|
22
|
+
const text = readText(file);
|
|
23
|
+
if (!text) continue;
|
|
24
|
+
let offset = 0, lineNo = 0;
|
|
25
|
+
for (const raw of text.split("\n")) {
|
|
26
|
+
lineNo += 1;
|
|
27
|
+
// A header inside a string literal is not a header: strip literals before judging the line.
|
|
28
|
+
const match = TELEMETRY_HEADER.exec(raw.replace(STRING_ON_LINE, '""'));
|
|
29
|
+
if (match) {
|
|
30
|
+
// Locate on the RAW line: literal stripping can shift columns, and the finding must not.
|
|
31
|
+
const column = Math.max(raw.indexOf(match[1]) + 1, 1);
|
|
32
|
+
if (!concreteUrn.test(match[1])) {
|
|
33
|
+
violations.push({ rule_id: RULE, file, line: lineNo, col: column,
|
|
34
|
+
evidence: `${match[1]} is not a concrete telemetry URN telemetry:{kind}:{plane}:{theme}:{artifact}[:{measure}]`,
|
|
35
|
+
source_line: raw.trim() });
|
|
36
|
+
} else if (!ids.has(match[1])) {
|
|
37
|
+
violations.push({ rule_id: RULE, file, line: lineNo, col: column,
|
|
38
|
+
evidence: `${match[1]} does not resolve to a tracking-plan item under telemetry/`,
|
|
39
|
+
source_line: raw.trim() });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
offset += raw.length + 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
process.stderr.write(`bun-telemetry[source-binding]: ${violations.length} violation(s)\n`);
|
|
48
|
+
emit(violations);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Member check: coder.bun.telemetry-vendor-sdk (telemetry code family)
|
|
3
|
+
//
|
|
4
|
+
// CONTRACT (v1.1): reads ATDD_SCAN_ROOTS / ATDD_SCAN_EXCLUDES, writes RAW violations
|
|
5
|
+
// to ATDD_VIOLATIONS_REPORT, exits 0 regardless of count.
|
|
6
|
+
import { walk, readRoots, readExcludes, readText, emit, locate, SOURCE_EXT } from "../../../lib/scan.mjs";
|
|
7
|
+
import { allImportSpecifiers, layerOf } from "../../../lib/imports.mjs";
|
|
8
|
+
import { registry } from "../registry.mjs";
|
|
9
|
+
|
|
10
|
+
// Core depends only on a neutral TelemetryPort. Vendor SDKs and exporters belong in
|
|
11
|
+
// the outer infrastructure adapters: the integration layer, or an infrastructure/
|
|
12
|
+
// directory. A layered file that is neither has no business importing a vendor SDK —
|
|
13
|
+
// type-only imports included, because the coupling is real even when erased.
|
|
14
|
+
const RULE = "coder.bun.telemetry-vendor-sdk";
|
|
15
|
+
const VENDOR_SDK = /^(?:@opentelemetry\/|@sentry\/|@datadog\/|@posthog\/|@statsig\/|@braze\/|@heap\/|@rudderstack\/|@segment\/|@amplitude\/|@mixpanel\/|dd-trace$|newrelic$|segment$|mixpanel$|amplitude$)/;
|
|
16
|
+
const INFRASTRUCTURE = /[/\\]infrastructure[/\\]/;
|
|
17
|
+
|
|
18
|
+
const { adopted } = await registry();
|
|
19
|
+
const violations = [];
|
|
20
|
+
if (adopted) {
|
|
21
|
+
const excludes = readExcludes();
|
|
22
|
+
for (const root of readRoots()) {
|
|
23
|
+
for (const file of walk(root, excludes, SOURCE_EXT)) {
|
|
24
|
+
const layer = layerOf(file);
|
|
25
|
+
if (!layer || layer === "integration" || INFRASTRUCTURE.test(file)) continue;
|
|
26
|
+
const text = readText(file);
|
|
27
|
+
if (!text) continue;
|
|
28
|
+
for (const specifier of allImportSpecifiers(text)) {
|
|
29
|
+
if (!VENDOR_SDK.test(specifier)) continue;
|
|
30
|
+
const at = text.indexOf(specifier);
|
|
31
|
+
violations.push({ rule_id: RULE, file, ...locate(text, at === -1 ? 0 : at),
|
|
32
|
+
evidence: `${layer} layer imports telemetry vendor SDK '${specifier}'; core depends only on the TelemetryPort — wire exporters in infrastructure adapters (integration layer)` });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
process.stderr.write(`bun-telemetry[vendor-sdk]: ${violations.length} violation(s)\n`);
|
|
38
|
+
emit(violations);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// FAMILY validator: bun_telemetry_code
|
|
3
|
+
// Runs each member check (checks/*.mjs) VERBATIM as a subprocess and merges their
|
|
4
|
+
// RAW v1.1 reports into one — one implementation realizing a family of rule_ids.
|
|
5
|
+
// Files whose name begins with `_` are skipped: they are shared helpers.
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
import { readFileSync, writeFileSync, mkdtempSync, readdirSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join, dirname } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const reportPath = process.env.ATDD_VIOLATIONS_REPORT;
|
|
14
|
+
if (!reportPath) {
|
|
15
|
+
process.stderr.write("family: ATDD_VIOLATIONS_REPORT is not set\n");
|
|
16
|
+
process.exit(2);
|
|
17
|
+
}
|
|
18
|
+
const checks = readdirSync(join(here, "checks"))
|
|
19
|
+
.filter((f) => (f.endsWith(".mjs") || f.endsWith(".ts")) && !f.startsWith("_"))
|
|
20
|
+
.sort();
|
|
21
|
+
const td = mkdtempSync(join(tmpdir(), "atdd-bun-fam-"));
|
|
22
|
+
const out = [];
|
|
23
|
+
let crashed = false;
|
|
24
|
+
for (const c of checks) {
|
|
25
|
+
const rep = join(td, c + ".json");
|
|
26
|
+
let exitedCleanly = true;
|
|
27
|
+
try {
|
|
28
|
+
execFileSync(process.execPath, [join(here, "checks", c)], {
|
|
29
|
+
env: { ...process.env, ATDD_VIOLATIONS_REPORT: rep },
|
|
30
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
31
|
+
});
|
|
32
|
+
} catch {
|
|
33
|
+
exitedCleanly = false; /* a member may exit non-zero after writing its report */
|
|
34
|
+
}
|
|
35
|
+
let read = false;
|
|
36
|
+
try {
|
|
37
|
+
out.push(...JSON.parse(readFileSync(rep, "utf8")).violations);
|
|
38
|
+
read = true;
|
|
39
|
+
} catch {}
|
|
40
|
+
// A member that crashed WITHOUT a report is a detector bug, and swallowing it here is a silent
|
|
41
|
+
// pass — the exact failure that once hid a broken regex behind zero findings. Fail loudly.
|
|
42
|
+
if (!exitedCleanly && !read) {
|
|
43
|
+
process.stderr.write(`family bun_telemetry_code: member ${c} crashed without a violation report\n`);
|
|
44
|
+
crashed = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (crashed) process.exit(2); // a crashed member is a detector bug: fail loudly, not silently
|
|
48
|
+
writeFileSync(reportPath, JSON.stringify({ violations: out }, null, 2), "utf8");
|
|
49
|
+
process.stderr.write("family bun_telemetry_code: " + out.length + " violation(s)\n");
|
|
50
|
+
process.exit(0);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
urn: wmbt:commons:E001
|
|
2
|
+
acceptances:
|
|
3
|
+
- identity:
|
|
4
|
+
urn: acc:commons:E001-UNIT-001
|
|
5
|
+
telemetry:
|
|
6
|
+
disposition: required
|
|
7
|
+
events:
|
|
8
|
+
- telemetry:event:be:commons:response-invocation-accepted
|
|
9
|
+
metrics:
|
|
10
|
+
- telemetry:metric:be:commons:response-invocation-accepted:duration
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Telemetry: telemetry:event:be:commons:response-invocation-accepted
|
|
2
|
+
// Telemetry: telemetry:metric:be:commons:response-invocation-accepted:duration
|
|
3
|
+
export type TelemetryPort = { emit(id: string, properties: Record<string, unknown>): void };
|
|
4
|
+
|
|
5
|
+
export function acceptResponse(response: { id: string }, telemetry: TelemetryPort): void {
|
|
6
|
+
telemetry.emit("telemetry:event:be:commons:response-invocation-accepted", { response_id: response.id, outcome: "accepted" });
|
|
7
|
+
telemetry.emit("telemetry:metric:be:commons:response-invocation-accepted:duration", { outcome: "accepted" });
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The one place a telemetry vendor SDK may appear: the outer adapter.
|
|
2
|
+
import { metrics } from "@opentelemetry/api";
|
|
3
|
+
|
|
4
|
+
export const otelTelemetry = {
|
|
5
|
+
emit(id: string, properties: Record<string, unknown>): void {
|
|
6
|
+
metrics.getMeter("commons").createCounter(id.replace(/^telemetry:/, "")).add(1, properties as Record<string, string>);
|
|
7
|
+
},
|
|
8
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "telemetry:event:be:commons:response-invocation-accepted",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"logical_artifact": "telemetry:commons:response-invocation-accepted",
|
|
5
|
+
"kind": "event",
|
|
6
|
+
"plane": "be",
|
|
7
|
+
"owner": "commons",
|
|
8
|
+
"purpose": "Records that a response invocation passed acceptance so ingress health can be audited.",
|
|
9
|
+
"acceptances": ["acc:commons:E001-UNIT-001"],
|
|
10
|
+
"properties": {
|
|
11
|
+
"response_id": { "type": "string", "classification": "internal", "cardinality": "high" },
|
|
12
|
+
"outcome": { "type": "string", "allowed_values": ["accepted"] }
|
|
13
|
+
},
|
|
14
|
+
"required": ["response_id", "outcome"],
|
|
15
|
+
"forbidden_properties": ["raw_payload", "secret", "prompt"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "telemetry:metric:be:commons:response-invocation-accepted:duration",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"logical_artifact": "telemetry:commons:response-invocation-accepted",
|
|
5
|
+
"kind": "metric",
|
|
6
|
+
"plane": "be",
|
|
7
|
+
"measure": "duration",
|
|
8
|
+
"instrument": "histogram",
|
|
9
|
+
"unit": "ms",
|
|
10
|
+
"owner": "commons",
|
|
11
|
+
"purpose": "Bounds how long an accepted response invocation takes to complete end to end.",
|
|
12
|
+
"acceptances": ["acc:commons:E001-UNIT-001"],
|
|
13
|
+
"properties": {
|
|
14
|
+
"outcome": { "type": "string", "classification": "internal", "cardinality": "low" }
|
|
15
|
+
},
|
|
16
|
+
"required": ["outcome"],
|
|
17
|
+
"dimensions": [{ "name": "outcome", "cardinality": "low" }],
|
|
18
|
+
"timing": ["post-commit"]
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// URN: test:commons:ingress:E001-UNIT-001
|
|
2
|
+
// Acceptance: acc:commons:E001-UNIT-001
|
|
3
|
+
// Telemetry: telemetry:event:be:commons:response-invocation-accepted
|
|
4
|
+
// Telemetry: telemetry:metric:be:commons:response-invocation-accepted:duration
|
|
5
|
+
import { expect, mock, test } from "bun:test";
|
|
6
|
+
import { acceptResponse } from "../../../../../src/wagons/commons/features/ingress/domain/accept-response";
|
|
7
|
+
|
|
8
|
+
test("emits the planned event once the invocation commits", () => {
|
|
9
|
+
const emit = mock(() => {});
|
|
10
|
+
acceptResponse({ id: "r-1" }, { emit });
|
|
11
|
+
expect(emit).toHaveBeenCalledWith("telemetry:event:be:commons:response-invocation-accepted", { response_id: "r-1", outcome: "accepted" });
|
|
12
|
+
expect(emit).toHaveBeenCalledWith("telemetry:metric:be:commons:response-invocation-accepted:duration", { outcome: "accepted" });
|
|
13
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Tests are not implementation source: the emit vocabulary is theirs to use freely.
|
|
2
|
+
import { expect, test } from "bun:test";
|
|
3
|
+
|
|
4
|
+
test("an accepted response emits the planned event", () => {
|
|
5
|
+
const emitted: Array<[string, Record<string, unknown>]> = [];
|
|
6
|
+
const sink = { emit: (id: string, properties: Record<string, unknown>) => emitted.push([id, properties]) };
|
|
7
|
+
sink.emit("fixture_only_probe_event", {});
|
|
8
|
+
expect(emitted).toContainEqual(["fixture_only_probe_event", {}]);
|
|
9
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Telemetry: telemetry:event:be:commons:unknown-item
|
|
2
|
+
// Telemetry: order-created
|
|
3
|
+
export function acceptResponse(
|
|
4
|
+
response: { id: string; raw: string },
|
|
5
|
+
telemetry: { emit(id: string, properties: Record<string, unknown>): void },
|
|
6
|
+
): void {
|
|
7
|
+
telemetry.emit("order_accepted", { orderId: 7 });
|
|
8
|
+
telemetry.emit("telemetry:event:be:commons:response-invocation-accepted", {
|
|
9
|
+
response_id: response.id,
|
|
10
|
+
raw_payload: response.raw,
|
|
11
|
+
secret: "k",
|
|
12
|
+
});
|
|
13
|
+
}
|