pumuki 6.3.32 → 6.3.34
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/docs/CONFIGURATION.md +18 -0
- package/docs/RELEASE_NOTES.md +16 -0
- package/docs/registro-maestro-de-seguimiento.md +1 -1
- package/docs/seguimiento-completo-validacion-ruralgo-03-03-2026.md +85 -4
- package/docs/validation/README.md +5 -0
- package/integrations/lifecycle/doctor.ts +142 -2
- package/integrations/telemetry/gateTelemetry.ts +53 -2
- package/package.json +1 -1
- package/scripts/enterprise-contract-suite-contract.ts +10 -1
- package/scripts/enterprise-contract-telemetry-rotation.ts +102 -0
- package/scripts/run-enterprise-contract-suite.ts +4 -1
package/docs/CONFIGURATION.md
CHANGED
|
@@ -104,6 +104,9 @@ Structured telemetry output is disabled by default and can be enabled with envir
|
|
|
104
104
|
- `PUMUKI_TELEMETRY_JSONL_PATH`:
|
|
105
105
|
- JSONL file path for `telemetry_event_v1` records.
|
|
106
106
|
- Accepts absolute path or repo-relative path.
|
|
107
|
+
- `PUMUKI_TELEMETRY_JSONL_MAX_BYTES`:
|
|
108
|
+
- Optional max size for JSONL file growth.
|
|
109
|
+
- When current file size plus next event exceeds this value, current file rotates to `<path>.1` before append.
|
|
107
110
|
- `PUMUKI_TELEMETRY_OTEL_ENDPOINT`:
|
|
108
111
|
- OTLP HTTP logs endpoint (`/v1/logs`).
|
|
109
112
|
- `PUMUKI_TELEMETRY_OTEL_SERVICE_NAME`:
|
|
@@ -116,6 +119,21 @@ Notes:
|
|
|
116
119
|
- You can enable JSONL only, OTel only, or both.
|
|
117
120
|
- If unset, no telemetry export is attempted.
|
|
118
121
|
- Gate execution remains deterministic even when OTel endpoint is unavailable (best-effort dispatch).
|
|
122
|
+
- Rotation is opt-in; without `PUMUKI_TELEMETRY_JSONL_MAX_BYTES`, append behavior remains unchanged.
|
|
123
|
+
|
|
124
|
+
Quick verification (JSONL):
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
PUMUKI_TELEMETRY_JSONL_PATH=.pumuki/artifacts/gate-telemetry.jsonl \
|
|
128
|
+
npx --yes --package pumuki@latest pumuki-pre-commit
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Expected JSONL keys for enterprise audit ingestion:
|
|
132
|
+
|
|
133
|
+
- `schema=telemetry_event_v1` with `schema_version=1.0`
|
|
134
|
+
- `stage`, `gate_outcome`, `severity_counts`
|
|
135
|
+
- `policy.bundle`, `policy.hash`, `policy.version`, `policy.signature`, `policy.policy_source`
|
|
136
|
+
- `policy.validation_status`, `policy.validation_code` (when policy-as-code validation is available)
|
|
119
137
|
|
|
120
138
|
## Heuristic pilot flag
|
|
121
139
|
|
package/docs/RELEASE_NOTES.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
This file tracks the active deterministic framework line used in this repository.
|
|
4
4
|
Detailed commit history remains available through Git history (`git log` / `git show`).
|
|
5
5
|
|
|
6
|
+
## 2026-03 (enterprise hardening updates)
|
|
7
|
+
|
|
8
|
+
### 2026-03-04 (v6.3.33)
|
|
9
|
+
|
|
10
|
+
- Runtime hardening shipped for enterprise diagnosis:
|
|
11
|
+
- `pumuki doctor --deep --json` now includes explicit compatibility contract payload under `deep.contract`.
|
|
12
|
+
- New deep check id `compatibility-contract` validates the active contract for `pumuki/openspec/hooks/adapter`.
|
|
13
|
+
- Traceability:
|
|
14
|
+
- implementation PR: `#563`
|
|
15
|
+
- release PR: `#567`
|
|
16
|
+
- Consumer quick verification:
|
|
17
|
+
- `npx --yes --package pumuki@latest pumuki doctor --deep --json`
|
|
18
|
+
- expected signal in JSON:
|
|
19
|
+
- `deep.checks` contains an item with `id=compatibility-contract`
|
|
20
|
+
- `deep.contract.overall` resolves to `compatible` or `incompatible` deterministically
|
|
21
|
+
|
|
6
22
|
## 2026-02 (enterprise-refactor updates)
|
|
7
23
|
|
|
8
24
|
### 2026-02-27 (v6.3.24)
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
## Estado actual
|
|
8
8
|
- Plan activo: `docs/seguimiento-completo-validacion-ruralgo-03-03-2026.md`
|
|
9
9
|
- Estado del plan: EN CURSO
|
|
10
|
-
- Task activa (`🚧`): `P12.F2.
|
|
10
|
+
- Task activa (`🚧`): `P12.F2.T60` (release patch con mejoras de telemetría, issue `#578`).
|
|
11
11
|
|
|
12
12
|
## Historial resumido
|
|
13
13
|
- No se mantienen MDs históricos de seguimiento en este repositorio.
|
|
@@ -1757,11 +1757,92 @@ Criterio de salida F5:
|
|
|
1757
1757
|
- `npm run -s validation:contract-suite:enterprise -- --json`
|
|
1758
1758
|
- `npm run -s validation:tracking-single-active`
|
|
1759
1759
|
|
|
1760
|
-
-
|
|
1760
|
+
- ✅ `P12.F2.T53` Publicar patch release con la ampliación de suite contractual (`#557`) y dejar disponibilidad inmediata para consumidores.
|
|
1761
|
+
- cierre ejecutado:
|
|
1762
|
+
- rama release creada y mergeada: `release/6.3.32` -> `#560` (`https://github.com/SwiftEnProfundidad/ast-intelligence-hooks/pull/560`).
|
|
1763
|
+
- publicación npm completada: `pumuki@6.3.32`.
|
|
1764
|
+
- evidencia:
|
|
1765
|
+
- `npx --yes tsx@4.21.0 --test scripts/__tests__/enterprise-contract-suite-contract.test.ts scripts/__tests__/enterprise-contract-suite-args-lib.test.ts scripts/__tests__/enterprise-contract-suite-report-lib.test.ts`
|
|
1766
|
+
- `npm run -s typecheck`
|
|
1767
|
+
- `npm run -s validation:contract-suite:enterprise -- --json`
|
|
1768
|
+
- `npm run -s validation:package-manifest`
|
|
1769
|
+
- `npm publish --access public`
|
|
1770
|
+
- `npm view pumuki version` => `6.3.32`
|
|
1771
|
+
|
|
1772
|
+
- ✅ `P12.F2.T54` Ejecutar siguiente mejora estratégica: contrato explícito de compatibilidad (pumuki/openspec/hooks/adapters) con validación automática en `doctor`.
|
|
1773
|
+
- cierre ejecutado:
|
|
1774
|
+
- issue creada y cerrada: `#562`.
|
|
1775
|
+
- rama técnica creada y mergeada: `feature/562-doctor-deep-compatibility-contract` -> `#563` (`https://github.com/SwiftEnProfundidad/ast-intelligence-hooks/pull/563`).
|
|
1776
|
+
- commit de merge en `develop`: `a1df815`.
|
|
1777
|
+
- evidencia:
|
|
1778
|
+
- `npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/doctor.test.ts integrations/lifecycle/__tests__/cli.test.ts`
|
|
1779
|
+
- `npm run -s typecheck`
|
|
1780
|
+
- `npm run -s validation:contract-suite:enterprise -- --json`
|
|
1781
|
+
- `npm run -s validation:tracking-single-active`
|
|
1782
|
+
|
|
1783
|
+
- ✅ `P12.F2.T55` Publicar patch release con el contrato de compatibilidad de `doctor --deep` ya mergeado en `#563`.
|
|
1784
|
+
- cierre ejecutado:
|
|
1785
|
+
- issue creada y cerrada: `#565`.
|
|
1786
|
+
- rama release creada y mergeada: `release/6.3.33` -> `#567` (`https://github.com/SwiftEnProfundidad/ast-intelligence-hooks/pull/567`).
|
|
1787
|
+
- publicación npm completada: `pumuki@6.3.33`.
|
|
1788
|
+
- commit de merge en `develop`: `6f36830`.
|
|
1789
|
+
- evidencia:
|
|
1790
|
+
- `npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/doctor.test.ts integrations/lifecycle/__tests__/cli.test.ts`
|
|
1791
|
+
- `npm run -s typecheck`
|
|
1792
|
+
- `npm run -s validation:contract-suite:enterprise -- --json`
|
|
1793
|
+
- `npm run -s validation:package-manifest`
|
|
1794
|
+
- `npm publish --access public`
|
|
1795
|
+
- `npm view pumuki version` => `6.3.33`
|
|
1796
|
+
|
|
1797
|
+
- ✅ `P12.F2.T56` Publicar documentación de adopción de `6.3.33` (release notes + verificación operativa rápida).
|
|
1798
|
+
- cierre ejecutado:
|
|
1799
|
+
- issue documental creada: `#568`.
|
|
1800
|
+
- release notes actualizadas con sección `2026-03-04 (v6.3.33)` y comandos de verificación para consumidores.
|
|
1801
|
+
- documentación operativa alineada con el release publicado `6.3.33`.
|
|
1802
|
+
- evidencia:
|
|
1803
|
+
- `docs/RELEASE_NOTES.md` incluye delta funcional de `doctor --deep` para `compatibility-contract`.
|
|
1804
|
+
- `npm view pumuki version` => `6.3.33`
|
|
1805
|
+
- `npm run -s validation:tracking-single-active`
|
|
1806
|
+
|
|
1807
|
+
- ✅ `P12.F2.T57` Ejecutar siguiente mejora estratégica: export de telemetría de gates en JSONL determinista para auditoría enterprise.
|
|
1808
|
+
- cierre ejecutado:
|
|
1809
|
+
- issue creada y cerrada: `#569`.
|
|
1810
|
+
- rama técnica creada y mergeada: `feature/569-telemetry-jsonl-audit-hardening` -> `#571` (`https://github.com/SwiftEnProfundidad/ast-intelligence-hooks/pull/571`).
|
|
1811
|
+
- commit de merge en `develop`: `b98ef25`.
|
|
1812
|
+
- evidencia:
|
|
1813
|
+
- `npx --yes tsx@4.21.0 --test integrations/telemetry/__tests__/gateTelemetry.test.ts`
|
|
1814
|
+
- `npm run -s typecheck`
|
|
1815
|
+
- `npm run -s validation:tracking-single-active`
|
|
1816
|
+
- `docs/CONFIGURATION.md` actualizado con verificación operativa JSONL y claves esperadas.
|
|
1817
|
+
|
|
1818
|
+
- ✅ `P12.F2.T58` Ejecutar siguiente mejora estratégica: rotación/guard de tamaño para telemetría JSONL en repos enterprise de larga duración.
|
|
1819
|
+
- cierre ejecutado:
|
|
1820
|
+
- issue creada y cerrada: `#572`.
|
|
1821
|
+
- rama técnica creada y mergeada: `feature/572-telemetry-jsonl-rotation-guard` -> `#574` (`https://github.com/SwiftEnProfundidad/ast-intelligence-hooks/pull/574`).
|
|
1822
|
+
- commit de merge en `develop`: `b3a5b27`.
|
|
1823
|
+
- evidencia:
|
|
1824
|
+
- `npx --yes tsx@4.21.0 --test integrations/telemetry/__tests__/gateTelemetry.test.ts`
|
|
1825
|
+
- `npm run -s typecheck`
|
|
1826
|
+
- `npm run -s validation:tracking-single-active`
|
|
1827
|
+
- `docs/CONFIGURATION.md` actualizado con `PUMUKI_TELEMETRY_JSONL_MAX_BYTES`.
|
|
1828
|
+
|
|
1829
|
+
- ✅ `P12.F2.T59` Ejecutar siguiente mejora estratégica: cubrir la rotación JSONL en la suite contractual enterprise.
|
|
1830
|
+
- cierre ejecutado:
|
|
1831
|
+
- issue creada y cerrada: `#575`.
|
|
1832
|
+
- rama técnica creada y mergeada: `feature/575-contract-suite-telemetry-rotation` -> `#577` (`https://github.com/SwiftEnProfundidad/ast-intelligence-hooks/pull/577`).
|
|
1833
|
+
- commit de merge en `develop`: `89e2045`.
|
|
1834
|
+
- evidencia:
|
|
1835
|
+
- `npx --yes tsx@4.21.0 --test scripts/__tests__/enterprise-contract-suite-contract.test.ts scripts/__tests__/enterprise-contract-suite-report-lib.test.ts scripts/__tests__/enterprise-contract-suite-args-lib.test.ts integrations/telemetry/__tests__/gateTelemetry.test.ts`
|
|
1836
|
+
- `npm run -s typecheck`
|
|
1837
|
+
- `npm run -s validation:contract-suite:enterprise -- --json`
|
|
1838
|
+
- `npm run -s validation:tracking-single-active`
|
|
1839
|
+
- `docs/validation/README.md` actualizado con perfiles activos de la suite contractual.
|
|
1840
|
+
|
|
1841
|
+
- 🚧 `P12.F2.T60` Publicar patch release con mejoras de telemetría (`#574`, `#577`) para disponibilidad inmediata en npm.
|
|
1761
1842
|
- salida esperada:
|
|
1762
|
-
-
|
|
1763
|
-
-
|
|
1764
|
-
-
|
|
1843
|
+
- issue de release creada con alcance/doD verificable (`#578`).
|
|
1844
|
+
- nueva versión npm publicada con rotación JSONL + profile contractual `telemetry-rotation`.
|
|
1845
|
+
- trazabilidad cerrada en plan activo + registro maestro.
|
|
1765
1846
|
|
|
1766
1847
|
Criterio de salida F6:
|
|
1767
1848
|
- veredicto final trazable y cierre administrativo completo.
|
|
@@ -16,6 +16,11 @@ Este directorio contiene solo documentación oficial y estable de validación pa
|
|
|
16
16
|
- Master de seguimiento: `docs/registro-maestro-de-seguimiento.md`.
|
|
17
17
|
- Plan activo: `docs/seguimiento-completo-validacion-ruralgo-03-03-2026.md`.
|
|
18
18
|
- Suite contractual enterprise (MVP): `npm run -s validation:contract-suite:enterprise`.
|
|
19
|
+
- Perfiles activos del reporte JSON:
|
|
20
|
+
- `minimal`
|
|
21
|
+
- `block`
|
|
22
|
+
- `minimal-repeat`
|
|
23
|
+
- `telemetry-rotation`
|
|
19
24
|
|
|
20
25
|
## Política de higiene
|
|
21
26
|
|
|
@@ -6,6 +6,11 @@ import { getPumukiHooksStatus } from './hookManager';
|
|
|
6
6
|
import { LifecycleGitService, type ILifecycleGitService } from './gitService';
|
|
7
7
|
import { getCurrentPumukiVersion } from './packageInfo';
|
|
8
8
|
import { readLifecycleState, type LifecycleState } from './state';
|
|
9
|
+
import {
|
|
10
|
+
detectOpenSpecInstallation,
|
|
11
|
+
evaluateOpenSpecCompatibility,
|
|
12
|
+
isOpenSpecProjectInitialized,
|
|
13
|
+
} from '../sdd/openSpecCli';
|
|
9
14
|
|
|
10
15
|
export type DoctorIssueSeverity = 'warning' | 'error';
|
|
11
16
|
|
|
@@ -18,7 +23,8 @@ export type DoctorDeepCheckId =
|
|
|
18
23
|
| 'upstream-readiness'
|
|
19
24
|
| 'adapter-wiring'
|
|
20
25
|
| 'policy-drift'
|
|
21
|
-
| 'evidence-source-drift'
|
|
26
|
+
| 'evidence-source-drift'
|
|
27
|
+
| 'compatibility-contract';
|
|
22
28
|
|
|
23
29
|
export type DoctorDeepCheck = {
|
|
24
30
|
id: DoctorDeepCheckId;
|
|
@@ -33,6 +39,30 @@ export type DoctorDeepReport = {
|
|
|
33
39
|
enabled: true;
|
|
34
40
|
checks: ReadonlyArray<DoctorDeepCheck>;
|
|
35
41
|
blocking: boolean;
|
|
42
|
+
contract: DoctorCompatibilityContract;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type DoctorCompatibilityContract = {
|
|
46
|
+
overall: 'compatible' | 'incompatible';
|
|
47
|
+
pumuki: {
|
|
48
|
+
installed: boolean;
|
|
49
|
+
version: string;
|
|
50
|
+
};
|
|
51
|
+
openspec: {
|
|
52
|
+
required: boolean;
|
|
53
|
+
installed: boolean;
|
|
54
|
+
version: string | null;
|
|
55
|
+
minimumVersion: string;
|
|
56
|
+
compatible: boolean;
|
|
57
|
+
};
|
|
58
|
+
hooks: {
|
|
59
|
+
managed: boolean;
|
|
60
|
+
managedCount: number;
|
|
61
|
+
totalCount: number;
|
|
62
|
+
};
|
|
63
|
+
adapter: {
|
|
64
|
+
valid: boolean;
|
|
65
|
+
};
|
|
36
66
|
};
|
|
37
67
|
|
|
38
68
|
export type LifecycleDoctorReport = {
|
|
@@ -457,21 +487,128 @@ const evaluateEvidenceSourceDriftCheck = (params: {
|
|
|
457
487
|
});
|
|
458
488
|
};
|
|
459
489
|
|
|
490
|
+
const buildCompatibilityContract = (params: {
|
|
491
|
+
repoRoot: string;
|
|
492
|
+
lifecycleState: LifecycleState;
|
|
493
|
+
hookStatus: ReturnType<typeof getPumukiHooksStatus>;
|
|
494
|
+
adapterCheck: DoctorDeepCheck;
|
|
495
|
+
}): DoctorCompatibilityContract => {
|
|
496
|
+
const hooks = Object.values(params.hookStatus);
|
|
497
|
+
const managedCount = hooks.filter((entry) => entry.managedBlockPresent).length;
|
|
498
|
+
const totalCount = hooks.length;
|
|
499
|
+
const hooksManaged = totalCount > 0 && managedCount === totalCount;
|
|
500
|
+
|
|
501
|
+
const openSpecRequired = isOpenSpecProjectInitialized(params.repoRoot);
|
|
502
|
+
const openSpecInstalled = detectOpenSpecInstallation(params.repoRoot);
|
|
503
|
+
const openSpecCompatibility = evaluateOpenSpecCompatibility(openSpecInstalled);
|
|
504
|
+
const openSpecCompatible = openSpecRequired ? openSpecCompatibility.compatible : true;
|
|
505
|
+
|
|
506
|
+
const adapterValid = params.adapterCheck.status === 'pass';
|
|
507
|
+
const pumukiInstalled = params.lifecycleState.installed === 'true';
|
|
508
|
+
const overallCompatible =
|
|
509
|
+
pumukiInstalled && hooksManaged && adapterValid && openSpecCompatible;
|
|
510
|
+
|
|
511
|
+
return {
|
|
512
|
+
overall: overallCompatible ? 'compatible' : 'incompatible',
|
|
513
|
+
pumuki: {
|
|
514
|
+
installed: pumukiInstalled,
|
|
515
|
+
version: getCurrentPumukiVersion(),
|
|
516
|
+
},
|
|
517
|
+
openspec: {
|
|
518
|
+
required: openSpecRequired,
|
|
519
|
+
installed: openSpecInstalled.installed,
|
|
520
|
+
version: openSpecInstalled.version ?? null,
|
|
521
|
+
minimumVersion: openSpecCompatibility.minimumVersion,
|
|
522
|
+
compatible: openSpecCompatible,
|
|
523
|
+
},
|
|
524
|
+
hooks: {
|
|
525
|
+
managed: hooksManaged,
|
|
526
|
+
managedCount,
|
|
527
|
+
totalCount,
|
|
528
|
+
},
|
|
529
|
+
adapter: {
|
|
530
|
+
valid: adapterValid,
|
|
531
|
+
},
|
|
532
|
+
};
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
const evaluateCompatibilityContractCheck = (
|
|
536
|
+
contract: DoctorCompatibilityContract
|
|
537
|
+
): DoctorDeepCheck => {
|
|
538
|
+
if (contract.overall === 'compatible') {
|
|
539
|
+
return buildDeepCheck({
|
|
540
|
+
id: 'compatibility-contract',
|
|
541
|
+
status: 'pass',
|
|
542
|
+
severity: 'info',
|
|
543
|
+
message: 'Compatibility contract is satisfied for pumuki/openspec/hooks/adapter.',
|
|
544
|
+
metadata: {
|
|
545
|
+
pumuki_installed: contract.pumuki.installed,
|
|
546
|
+
openspec_required: contract.openspec.required,
|
|
547
|
+
openspec_compatible: contract.openspec.compatible,
|
|
548
|
+
hooks_managed: contract.hooks.managed,
|
|
549
|
+
adapter_valid: contract.adapter.valid,
|
|
550
|
+
},
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const remediation: string[] = [];
|
|
555
|
+
if (!contract.pumuki.installed) {
|
|
556
|
+
remediation.push('Run "pumuki install" to restore lifecycle state.');
|
|
557
|
+
}
|
|
558
|
+
if (!contract.hooks.managed) {
|
|
559
|
+
remediation.push('Regenerate managed hooks with "pumuki install".');
|
|
560
|
+
}
|
|
561
|
+
if (!contract.adapter.valid) {
|
|
562
|
+
remediation.push('Repair adapter wiring with "pumuki adapter install --agent=codex".');
|
|
563
|
+
}
|
|
564
|
+
if (contract.openspec.required && !contract.openspec.compatible) {
|
|
565
|
+
remediation.push(
|
|
566
|
+
`Install or upgrade OpenSpec to >= ${contract.openspec.minimumVersion} before enterprise validation.`
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return buildDeepCheck({
|
|
571
|
+
id: 'compatibility-contract',
|
|
572
|
+
status: 'fail',
|
|
573
|
+
severity: 'warning',
|
|
574
|
+
message: 'Compatibility contract is not satisfied for one or more required components.',
|
|
575
|
+
remediation: remediation.join(' '),
|
|
576
|
+
metadata: {
|
|
577
|
+
pumuki_installed: contract.pumuki.installed,
|
|
578
|
+
openspec_required: contract.openspec.required,
|
|
579
|
+
openspec_compatible: contract.openspec.compatible,
|
|
580
|
+
hooks_managed: contract.hooks.managed,
|
|
581
|
+
adapter_valid: contract.adapter.valid,
|
|
582
|
+
},
|
|
583
|
+
});
|
|
584
|
+
};
|
|
585
|
+
|
|
460
586
|
const buildDoctorDeepReport = (params: {
|
|
461
587
|
git: ILifecycleGitService;
|
|
462
588
|
repoRoot: string;
|
|
589
|
+
lifecycleState: LifecycleState;
|
|
590
|
+
hookStatus: ReturnType<typeof getPumukiHooksStatus>;
|
|
463
591
|
}): DoctorDeepReport => {
|
|
592
|
+
const adapterCheck = evaluateAdapterWiringCheck(params.repoRoot);
|
|
593
|
+
const compatibilityContract = buildCompatibilityContract({
|
|
594
|
+
repoRoot: params.repoRoot,
|
|
595
|
+
lifecycleState: params.lifecycleState,
|
|
596
|
+
hookStatus: params.hookStatus,
|
|
597
|
+
adapterCheck,
|
|
598
|
+
});
|
|
599
|
+
|
|
464
600
|
const checks: DoctorDeepCheck[] = [
|
|
465
601
|
evaluateUpstreamReadinessCheck({
|
|
466
602
|
git: params.git,
|
|
467
603
|
repoRoot: params.repoRoot,
|
|
468
604
|
}),
|
|
469
|
-
|
|
605
|
+
adapterCheck,
|
|
470
606
|
evaluatePolicyDriftCheck(params.repoRoot),
|
|
471
607
|
evaluateEvidenceSourceDriftCheck({
|
|
472
608
|
git: params.git,
|
|
473
609
|
repoRoot: params.repoRoot,
|
|
474
610
|
}),
|
|
611
|
+
evaluateCompatibilityContractCheck(compatibilityContract),
|
|
475
612
|
];
|
|
476
613
|
|
|
477
614
|
return {
|
|
@@ -480,6 +617,7 @@ const buildDoctorDeepReport = (params: {
|
|
|
480
617
|
blocking: checks.some(
|
|
481
618
|
(check) => check.status === 'fail' && check.severity === 'error'
|
|
482
619
|
),
|
|
620
|
+
contract: compatibilityContract,
|
|
483
621
|
};
|
|
484
622
|
};
|
|
485
623
|
|
|
@@ -504,6 +642,8 @@ export const runLifecycleDoctor = (params?: {
|
|
|
504
642
|
? buildDoctorDeepReport({
|
|
505
643
|
git,
|
|
506
644
|
repoRoot,
|
|
645
|
+
lifecycleState,
|
|
646
|
+
hookStatus,
|
|
507
647
|
})
|
|
508
648
|
: undefined;
|
|
509
649
|
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
appendFileSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
renameSync,
|
|
6
|
+
statSync,
|
|
7
|
+
unlinkSync,
|
|
8
|
+
} from 'node:fs';
|
|
2
9
|
import { dirname, isAbsolute, join } from 'node:path';
|
|
3
10
|
import type { Finding } from '../../core/gate/Finding';
|
|
4
11
|
import type { GateOutcome } from '../../core/gate/GateOutcome';
|
|
@@ -76,6 +83,14 @@ const toOtelTimeoutMs = (value: string | undefined): number => {
|
|
|
76
83
|
return parsed;
|
|
77
84
|
};
|
|
78
85
|
|
|
86
|
+
const toTelemetryJsonlMaxBytes = (value: string | undefined): number | null => {
|
|
87
|
+
const parsed = Number.parseInt(value ?? '', 10);
|
|
88
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return parsed;
|
|
92
|
+
};
|
|
93
|
+
|
|
79
94
|
const resolveTargetPath = (repoRoot: string, targetPath: string): string => {
|
|
80
95
|
if (isAbsolute(targetPath)) {
|
|
81
96
|
return targetPath;
|
|
@@ -88,6 +103,31 @@ const defaultAppendJsonlLine = (targetPath: string, line: string): void => {
|
|
|
88
103
|
appendFileSync(targetPath, line, 'utf8');
|
|
89
104
|
};
|
|
90
105
|
|
|
106
|
+
const rotateTelemetryJsonlIfNeeded = (params: {
|
|
107
|
+
targetPath: string;
|
|
108
|
+
line: string;
|
|
109
|
+
maxBytes: number;
|
|
110
|
+
}): void => {
|
|
111
|
+
mkdirSync(dirname(params.targetPath), { recursive: true });
|
|
112
|
+
|
|
113
|
+
const lineBytes = Buffer.byteLength(params.line, 'utf8');
|
|
114
|
+
let currentSize = 0;
|
|
115
|
+
if (existsSync(params.targetPath)) {
|
|
116
|
+
currentSize = statSync(params.targetPath).size;
|
|
117
|
+
}
|
|
118
|
+
if (currentSize + lineBytes <= params.maxBytes) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const rotatedPath = `${params.targetPath}.1`;
|
|
123
|
+
if (existsSync(rotatedPath)) {
|
|
124
|
+
unlinkSync(rotatedPath);
|
|
125
|
+
}
|
|
126
|
+
if (existsSync(params.targetPath)) {
|
|
127
|
+
renameSync(params.targetPath, rotatedPath);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
91
131
|
const defaultPostOtelPayload = async (params: {
|
|
92
132
|
endpoint: string;
|
|
93
133
|
payload: unknown;
|
|
@@ -325,6 +365,9 @@ export const emitGateTelemetryEvent = async (
|
|
|
325
365
|
const otelTimeoutMs = toOtelTimeoutMs(
|
|
326
366
|
activeDependencies.env.PUMUKI_TELEMETRY_OTEL_TIMEOUT_MS
|
|
327
367
|
);
|
|
368
|
+
const telemetryJsonlMaxBytes = toTelemetryJsonlMaxBytes(
|
|
369
|
+
activeDependencies.env.PUMUKI_TELEMETRY_JSONL_MAX_BYTES
|
|
370
|
+
);
|
|
328
371
|
|
|
329
372
|
const event = toTelemetryEvent({
|
|
330
373
|
...params,
|
|
@@ -342,7 +385,15 @@ export const emitGateTelemetryEvent = async (
|
|
|
342
385
|
let jsonlPath: string | undefined;
|
|
343
386
|
if (jsonlPathRaw.length > 0) {
|
|
344
387
|
jsonlPath = resolveTargetPath(params.repoRoot, jsonlPathRaw);
|
|
345
|
-
|
|
388
|
+
const line = `${JSON.stringify(event)}\n`;
|
|
389
|
+
if (typeof telemetryJsonlMaxBytes === 'number') {
|
|
390
|
+
rotateTelemetryJsonlIfNeeded({
|
|
391
|
+
targetPath: jsonlPath,
|
|
392
|
+
line,
|
|
393
|
+
maxBytes: telemetryJsonlMaxBytes,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
activeDependencies.appendJsonlLine(jsonlPath, line);
|
|
346
397
|
}
|
|
347
398
|
|
|
348
399
|
let otelDispatched = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.34",
|
|
4
4
|
"description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
export type EnterpriseContractProfileId =
|
|
1
|
+
export type EnterpriseContractProfileId =
|
|
2
|
+
| 'minimal'
|
|
3
|
+
| 'block'
|
|
4
|
+
| 'minimal-repeat'
|
|
5
|
+
| 'telemetry-rotation';
|
|
2
6
|
|
|
3
7
|
export type EnterpriseContractProfileSpec = {
|
|
4
8
|
id: EnterpriseContractProfileId;
|
|
@@ -46,4 +50,9 @@ export const resolveEnterpriseContractProfiles = (): ReadonlyArray<EnterpriseCon
|
|
|
46
50
|
mode: 'minimal',
|
|
47
51
|
expectedExitCode: 1,
|
|
48
52
|
},
|
|
53
|
+
{
|
|
54
|
+
id: 'telemetry-rotation',
|
|
55
|
+
mode: 'minimal',
|
|
56
|
+
expectedExitCode: 0,
|
|
57
|
+
},
|
|
49
58
|
];
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import type { RepoState } from '../integrations/evidence/schema';
|
|
6
|
+
import { emitGateTelemetryEvent } from '../integrations/telemetry/gateTelemetry';
|
|
7
|
+
|
|
8
|
+
const buildRepoState = (repoRoot: string): RepoState => ({
|
|
9
|
+
repo_root: repoRoot,
|
|
10
|
+
git: {
|
|
11
|
+
available: true,
|
|
12
|
+
branch: 'feature/contract-suite-telemetry-rotation',
|
|
13
|
+
upstream: 'origin/feature/contract-suite-telemetry-rotation',
|
|
14
|
+
ahead: 0,
|
|
15
|
+
behind: 0,
|
|
16
|
+
dirty: false,
|
|
17
|
+
staged: 0,
|
|
18
|
+
unstaged: 0,
|
|
19
|
+
},
|
|
20
|
+
lifecycle: {
|
|
21
|
+
installed: true,
|
|
22
|
+
package_version: '6.3.33',
|
|
23
|
+
lifecycle_version: '6.3.33',
|
|
24
|
+
hooks: {
|
|
25
|
+
pre_commit: 'managed',
|
|
26
|
+
pre_push: 'managed',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const main = async (): Promise<void> => {
|
|
32
|
+
const workspaceRoot = mkdtempSync(join(tmpdir(), 'pumuki-contract-telemetry-rotation-'));
|
|
33
|
+
const jsonlRelativePath = '.pumuki/artifacts/gate-telemetry.jsonl';
|
|
34
|
+
const repoState = buildRepoState(workspaceRoot);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await emitGateTelemetryEvent(
|
|
38
|
+
{
|
|
39
|
+
stage: 'PRE_COMMIT',
|
|
40
|
+
auditMode: 'gate',
|
|
41
|
+
gateOutcome: 'PASS',
|
|
42
|
+
filesScanned: 1,
|
|
43
|
+
findings: [],
|
|
44
|
+
repoRoot: workspaceRoot,
|
|
45
|
+
repoState,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
env: {
|
|
49
|
+
PUMUKI_TELEMETRY_JSONL_PATH: jsonlRelativePath,
|
|
50
|
+
PUMUKI_TELEMETRY_JSONL_MAX_BYTES: '1',
|
|
51
|
+
} as NodeJS.ProcessEnv,
|
|
52
|
+
now: () => new Date('2026-03-04T00:00:00.000Z'),
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const secondEvent = await emitGateTelemetryEvent(
|
|
57
|
+
{
|
|
58
|
+
stage: 'PRE_PUSH',
|
|
59
|
+
auditMode: 'gate',
|
|
60
|
+
gateOutcome: 'BLOCK',
|
|
61
|
+
filesScanned: 2,
|
|
62
|
+
findings: [],
|
|
63
|
+
repoRoot: workspaceRoot,
|
|
64
|
+
repoState,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
env: {
|
|
68
|
+
PUMUKI_TELEMETRY_JSONL_PATH: jsonlRelativePath,
|
|
69
|
+
PUMUKI_TELEMETRY_JSONL_MAX_BYTES: '1',
|
|
70
|
+
} as NodeJS.ProcessEnv,
|
|
71
|
+
now: () => new Date('2026-03-04T00:00:01.000Z'),
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
assert.ok(secondEvent.jsonl_path);
|
|
76
|
+
const currentPath = secondEvent.jsonl_path;
|
|
77
|
+
const rotatedPath = `${currentPath}.1`;
|
|
78
|
+
assert.equal(existsSync(currentPath), true);
|
|
79
|
+
assert.equal(existsSync(rotatedPath), true);
|
|
80
|
+
|
|
81
|
+
const currentPayload = JSON.parse(readFileSync(currentPath, 'utf8').trim()) as {
|
|
82
|
+
schema?: string;
|
|
83
|
+
stage?: string;
|
|
84
|
+
};
|
|
85
|
+
const rotatedPayload = JSON.parse(readFileSync(rotatedPath, 'utf8').trim()) as {
|
|
86
|
+
schema?: string;
|
|
87
|
+
stage?: string;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
assert.equal(currentPayload.schema, 'telemetry_event_v1');
|
|
91
|
+
assert.equal(currentPayload.stage, 'PRE_PUSH');
|
|
92
|
+
assert.equal(rotatedPayload.schema, 'telemetry_event_v1');
|
|
93
|
+
assert.equal(rotatedPayload.stage, 'PRE_COMMIT');
|
|
94
|
+
} finally {
|
|
95
|
+
rmSync(workspaceRoot, {
|
|
96
|
+
recursive: true,
|
|
97
|
+
force: true,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
void main();
|
|
@@ -19,7 +19,10 @@ const runProfile = (
|
|
|
19
19
|
repoRoot: string,
|
|
20
20
|
profile: EnterpriseContractProfileSpec
|
|
21
21
|
): EnterpriseContractProfileResult => {
|
|
22
|
-
const args =
|
|
22
|
+
const args =
|
|
23
|
+
profile.id === 'telemetry-rotation'
|
|
24
|
+
? ['--import', 'tsx', 'scripts/enterprise-contract-telemetry-rotation.ts']
|
|
25
|
+
: ['--import', 'tsx', 'scripts/package-install-smoke.ts', `--mode=${profile.mode}`];
|
|
23
26
|
const result = runCommand({
|
|
24
27
|
cwd: repoRoot,
|
|
25
28
|
executable: 'node',
|