diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe0d2cb..d5bd6c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,8 +14,6 @@ on: - pinned - latest default: pinned - schedule: - - cron: '30 21 * * *' permissions: contents: read @@ -50,7 +48,7 @@ jobs: id: resolve env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REQUESTED_MODE: ${{ github.event_name == 'schedule' && 'latest' || inputs.upstream_mode || 'pinned' }} + REQUESTED_MODE: ${{ inputs.upstream_mode || 'pinned' }} run: | set -euo pipefail source compatibility/upstream.env @@ -138,7 +136,7 @@ jobs: - name: Validate Core customization env: - VALIDATION_RACE: ${{ github.event_name == 'schedule' && '1' || '0' }} + VALIDATION_RACE: ${{ needs.resolve-upstream.outputs.mode == 'latest' && '1' || '0' }} run: bash customizations-repo/scripts/validation/core.sh upstream-core source-image-build: diff --git a/cliproxyapi-pro-management/apply_customizations.py b/cliproxyapi-pro-management/apply_customizations.py index 6283728..f2552a3 100644 --- a/cliproxyapi-pro-management/apply_customizations.py +++ b/cliproxyapi-pro-management/apply_customizations.py @@ -225,19 +225,17 @@ }, } -def load_overlay_replacement_manifest(path: Path) -> tuple[dict[str, set[str]], dict[str, str]]: +def load_overlay_replacement_manifest(path: Path) -> dict[str, set[str]]: payload = json.loads(path.read_text()) if payload.get('schemaVersion') != 1 or not isinstance(payload.get('replacements'), list): raise RuntimeError(f'Invalid overlay replacement manifest: {path}') upstream_hashes: dict[str, set[str]] = {} - overlay_hashes: dict[str, str] = {} for entry in payload['replacements']: if not isinstance(entry, dict): raise RuntimeError(f'Invalid overlay replacement entry: {entry!r}') relative_path = entry.get('path') upstream = entry.get('upstreamSha256') - overlay = entry.get('overlaySha256') if ( not isinstance(relative_path, str) or not relative_path @@ -247,18 +245,13 @@ def load_overlay_replacement_manifest(path: Path) -> tuple[dict[str, set[str]], or not isinstance(upstream, list) or not upstream or not all(isinstance(item, str) and len(item) == 64 for item in upstream) - or not isinstance(overlay, str) - or len(overlay) != 64 ): raise RuntimeError(f'Invalid overlay replacement entry: {entry!r}') upstream_hashes[relative_path] = set(upstream) - overlay_hashes[relative_path] = overlay - return upstream_hashes, overlay_hashes + return upstream_hashes -OVERLAY_REPLACEMENT_HASHES, OVERLAY_REPLACEMENT_SOURCE_HASHES = load_overlay_replacement_manifest( - OVERLAY_REPLACEMENTS_FILE -) +OVERLAY_REPLACEMENT_HASHES = load_overlay_replacement_manifest(OVERLAY_REPLACEMENTS_FILE) _writes = {} @@ -370,18 +363,6 @@ def insert_once(path: Path, marker: str, insertion: str, present: str) -> None: def validate_overlay_collisions(target: Path) -> None: - if set(OVERLAY_REPLACEMENT_HASHES) != set(OVERLAY_REPLACEMENT_SOURCE_HASHES): - raise RuntimeError('Overlay replacement manifest hash maps are inconsistent') - for relative_path, expected_digest in OVERLAY_REPLACEMENT_SOURCE_HASHES.items(): - source = OVERLAY_DIR / relative_path - if not source.is_file(): - raise RuntimeError(f'Overlay replacement source is missing: {source}') - actual_digest = hashlib.sha256(source.read_bytes()).hexdigest() - if actual_digest != expected_digest: - raise RuntimeError( - f'Overlay replacement changed without reviewed manifest update: {source} ({actual_digest})' - ) - for src in OVERLAY_DIR.rglob('*'): if src.is_dir(): continue diff --git a/cliproxyapi-pro-management/overlay-replacements.json b/cliproxyapi-pro-management/overlay-replacements.json index 6c71460..9c77f17 100644 --- a/cliproxyapi-pro-management/overlay-replacements.json +++ b/cliproxyapi-pro-management/overlay-replacements.json @@ -10,7 +10,6 @@ "upstreamSha256": [ "52cbd98311904f87f588931114b7a708922e2c3d9049da39eb325be5902ee42b" ], - "overlaySha256": "c96370f6f75f0cfb4a77e6910edaa8762af8337a38f6d1e8c551e37a74772917", "reason": "Preserve the reviewed Pro request executor contract." }, { @@ -18,7 +17,6 @@ "upstreamSha256": [ "8fcded3c9edd03ac1f156abdfc37421df2bc0f6bba3717ca13664edd8f837b92" ], - "overlaySha256": "04fab80ec51e5d0d67ddf8d6332ea3e39d8bd5a337741734974a97d11e1195a0", "reason": "Preserve reviewed Pro quota normalization and provider resolution." }, { @@ -26,7 +24,6 @@ "upstreamSha256": [ "aef0e8b377e569294d5d5f291ce6da10b7b12322516d40f851f2b84521b7840b" ], - "overlaySha256": "6fa259e36db125eba8753820997e1e6c383488ec30cf5276f046479ce054b754", "reason": "Preserve reviewed Pro quota validation semantics." } ] diff --git a/cliproxyapi-pro-management/tests/test_account_inspection_module_boundaries.py b/cliproxyapi-pro-management/tests/test_account_inspection_module_boundaries.py deleted file mode 100644 index 3d4afd9..0000000 --- a/cliproxyapi-pro-management/tests/test_account_inspection_module_boundaries.py +++ /dev/null @@ -1,33 +0,0 @@ -import unittest -from pathlib import Path - - -OVERLAY_ROOT = Path(__file__).resolve().parents[1] / 'overlay/src' -PAGE_PATH = OVERLAY_ROOT / 'pages/AccountInspectionPage.tsx' -FEATURE_ROOT = OVERLAY_ROOT / 'features/monitoring' - - -class AccountInspectionModuleBoundariesTest(unittest.TestCase): - def test_page_remains_a_workflow_controller(self) -> None: - source = PAGE_PATH.read_text() - - self.assertLess(len(source.splitlines()), 2300) - self.assertNotIn('const buildInspectionResultsViewState =', source) - self.assertNotIn('const scheduleAuthFileAccountStats =', source) - self.assertNotIn('const inspectionBackendReducer =', source) - self.assertNotIn('function InspectionErrorDetailsPanel(', source) - - def test_feature_owns_page_model_and_styles(self) -> None: - model = (FEATURE_ROOT / 'accountInspectionPageModel.tsx').read_text() - self.assertIn('export const buildInspectionResultsViewState', model) - self.assertIn('export const inspectionBackendReducer', model) - self.assertTrue((FEATURE_ROOT / 'accountInspection.module.scss').is_file()) - self.assertFalse((OVERLAY_ROOT / 'pages/AccountInspectionPage.module.scss').exists()) - self.assertEqual( - len(list((FEATURE_ROOT / 'account-inspection-styles').glob('*.scss'))), - 6, - ) - - -if __name__ == '__main__': - unittest.main() diff --git a/cliproxyapi-pro-management/tests/test_monitoring_module_boundaries.py b/cliproxyapi-pro-management/tests/test_monitoring_module_boundaries.py deleted file mode 100644 index e1e5672..0000000 --- a/cliproxyapi-pro-management/tests/test_monitoring_module_boundaries.py +++ /dev/null @@ -1,45 +0,0 @@ -import unittest -from pathlib import Path - - -OVERLAY_ROOT = Path(__file__).resolve().parents[1] / 'overlay/src' -PAGE_PATH = OVERLAY_ROOT / 'pages/MonitoringCenterPage.tsx' - - -class MonitoringModuleBoundariesTest(unittest.TestCase): - def test_page_remains_a_controller_instead_of_reabsorbing_extracted_logic(self) -> None: - source = PAGE_PATH.read_text() - - self.assertLess(len(source.splitlines()), 2300) - self.assertNotIn('const buildUsageTrendAnalytics =', source) - self.assertNotIn('const buildRealtimeLogPageRows =', source) - self.assertNotIn('const normalizeRealtimeLogColumns =', source) - self.assertNotIn('function RealtimeCostCell(', source) - self.assertNotIn('function UsageTrendPanel(', source) - self.assertNotIn('function MonitoringHealthStatusBar(', source) - self.assertNotIn('function AccountStatsPanel(', source) - self.assertNotIn('open={isMonitoringSettingsOpen}', source) - self.assertNotIn('open={isPriceModalOpen}', source) - - def test_extracted_modules_own_their_domain_contracts(self) -> None: - analytics = (OVERLAY_ROOT / 'features/monitoring/monitoringAnalytics.ts').read_text() - realtime = (OVERLAY_ROOT / 'features/monitoring/realtimeLogPresentation.ts').read_text() - preferences = (OVERLAY_ROOT / 'features/monitoring/realtimeLogPreferences.ts').read_text() - health = (OVERLAY_ROOT / 'features/monitoring/accountHealth.ts').read_text() - - self.assertIn('export const buildUsageTrendAnalytics', analytics) - self.assertIn('export const buildRealtimeLogPageRows', realtime) - self.assertIn('export type RealtimeLogRow', realtime) - self.assertIn('export const normalizeRealtimeLogColumns', preferences) - self.assertIn('export const buildAccountStatusData', health) - - def test_monitoring_feature_owns_shared_styles(self) -> None: - feature_root = OVERLAY_ROOT / 'features/monitoring' - self.assertTrue((feature_root / 'monitoring.module.scss').is_file()) - self.assertFalse((OVERLAY_ROOT / 'pages/MonitoringCenterPage.module.scss').exists()) - for component in (feature_root / 'components').glob('*.tsx'): - self.assertNotIn("@/pages/MonitoringCenterPage.module.scss", component.read_text()) - - -if __name__ == '__main__': - unittest.main() diff --git a/cliproxyapi-pro-management/tests/test_overlay_collision_customization.py b/cliproxyapi-pro-management/tests/test_overlay_collision_customization.py index b33e11c..341bca4 100644 --- a/cliproxyapi-pro-management/tests/test_overlay_collision_customization.py +++ b/cliproxyapi-pro-management/tests/test_overlay_collision_customization.py @@ -16,12 +16,10 @@ class OverlayCollisionCustomizationTest(unittest.TestCase): def setUp(self) -> None: self.original_overlay_dir = CUSTOMIZATIONS.OVERLAY_DIR self.original_hashes = CUSTOMIZATIONS.OVERLAY_REPLACEMENT_HASHES - self.original_source_hashes = CUSTOMIZATIONS.OVERLAY_REPLACEMENT_SOURCE_HASHES def tearDown(self) -> None: CUSTOMIZATIONS.OVERLAY_DIR = self.original_overlay_dir CUSTOMIZATIONS.OVERLAY_REPLACEMENT_HASHES = self.original_hashes - CUSTOMIZATIONS.OVERLAY_REPLACEMENT_SOURCE_HASHES = self.original_source_hashes def test_allows_reviewed_replacement_and_idempotent_reapplication(self) -> None: with tempfile.TemporaryDirectory() as temp_dir: @@ -41,9 +39,6 @@ def test_allows_reviewed_replacement_and_idempotent_reapplication(self) -> None: CUSTOMIZATIONS.OVERLAY_REPLACEMENT_HASHES = { relative_path.as_posix(): {upstream_hash}, } - CUSTOMIZATIONS.OVERLAY_REPLACEMENT_SOURCE_HASHES = { - relative_path.as_posix(): hashlib.sha256(source.read_bytes()).hexdigest(), - } CUSTOMIZATIONS.copy_overlay(target) self.assertEqual('customized\n', destination.read_text()) @@ -67,9 +62,6 @@ def test_rejects_unreviewed_upstream_change(self) -> None: CUSTOMIZATIONS.OVERLAY_REPLACEMENT_HASHES = { relative_path.as_posix(): {'not-the-current-hash'}, } - CUSTOMIZATIONS.OVERLAY_REPLACEMENT_SOURCE_HASHES = { - relative_path.as_posix(): hashlib.sha256(source.read_bytes()).hexdigest(), - } with self.assertRaisesRegex(RuntimeError, 'Upstream overlay replacement changed'): CUSTOMIZATIONS.copy_overlay(target) @@ -89,7 +81,6 @@ def test_rejects_new_overlay_collision(self) -> None: CUSTOMIZATIONS.OVERLAY_DIR = overlay CUSTOMIZATIONS.OVERLAY_REPLACEMENT_HASHES = {} - CUSTOMIZATIONS.OVERLAY_REPLACEMENT_SOURCE_HASHES = {} with self.assertRaisesRegex(RuntimeError, 'Unexpected overlay collision'): CUSTOMIZATIONS.copy_overlay(target) @@ -110,36 +101,11 @@ def test_preflight_does_not_copy_other_files_before_rejecting(self) -> None: CUSTOMIZATIONS.OVERLAY_DIR = overlay CUSTOMIZATIONS.OVERLAY_REPLACEMENT_HASHES = {} - CUSTOMIZATIONS.OVERLAY_REPLACEMENT_SOURCE_HASHES = {} with self.assertRaisesRegex(RuntimeError, 'Unexpected overlay collision'): CUSTOMIZATIONS.copy_overlay(target) self.assertFalse((target / 'src/new.ts').exists()) - def test_rejects_unreviewed_overlay_replacement_change(self) -> None: - with tempfile.TemporaryDirectory() as temp_dir: - root = Path(temp_dir) - overlay = root / 'overlay' - target = root / 'target' - relative_path = Path('src/existing.ts') - source = overlay / relative_path - destination = target / relative_path - source.parent.mkdir(parents=True) - destination.parent.mkdir(parents=True) - source.write_text('changed customization\n') - destination.write_text('upstream\n') - - CUSTOMIZATIONS.OVERLAY_DIR = overlay - CUSTOMIZATIONS.OVERLAY_REPLACEMENT_HASHES = { - relative_path.as_posix(): {hashlib.sha256(destination.read_bytes()).hexdigest()}, - } - CUSTOMIZATIONS.OVERLAY_REPLACEMENT_SOURCE_HASHES = { - relative_path.as_posix(): 'not-the-current-overlay-hash', - } - - with self.assertRaisesRegex(RuntimeError, 'changed without reviewed manifest update'): - CUSTOMIZATIONS.copy_overlay(target) - if __name__ == '__main__': unittest.main() diff --git a/docs/code-review/2026-07.md b/docs/code-review/2026-07.md index 33e3ea5..9c80b3d 100644 --- a/docs/code-review/2026-07.md +++ b/docs/code-review/2026-07.md @@ -20,10 +20,11 @@ The pinned PR baseline lives in `compatibility/upstream.env`. Pull requests and | 6 | Handler lifecycle, physical request cancellation, and reconnect detail recovery | Completed | | 7 | Consistent backup snapshots and explicit legacy-restore trust gates | Completed | | 8 | Stable server-side log search and realtime controller extraction | Completed | -| 9 | Reproducible Core builds and bidirectional overlay provenance gates | Completed | -| 10 | Monitoring page decomposition, duplicate removal, and module-boundary guards | Completed | +| 9 | Deterministic Core archives and upstream overlay provenance gates | Completed | +| 10 | Monitoring page decomposition and duplicate removal | Completed | | 11 | Monitoring controller, modal, account-quota, and stylesheet decomposition | Completed | | 12 | Account-inspection model, reducer, and stylesheet decomposition | Completed | +| 13 | Post-review CI, provenance, and implementation-guard simplification | Completed | ## Severity @@ -46,11 +47,11 @@ The pinned PR baseline lives in `compatibility/upstream.env`. Pull requests and | PATCH-003 | P1 | Generator preflight | Reapplying the Core generator failed only after processing had started | A reused checkout could be left partially customized | Run the generator twice against one checkout | Reject an existing Pro sentinel before writes and verify the rejected run leaves the diff unchanged | `scripts/validation/core.sh` | Resolved | | PATCH-004 | P1 | Management overlay | Three overlay files replaced existing upstream files, and new path collisions were not detected | Upstream behavior could be overwritten without a merge review | Compare overlay paths with the upstream tree | Preflight reviewed source hashes, reject new collisions, and verify full-application idempotence | Overlay collision unit tests and Management validation | Resolved | | PATCH-005 | P1 | Patch anchors | Single-replacement helpers silently selected the first of multiple matching strings | Upstream duplication could redirect a patch to the wrong structure | The Gemini CLI quota anchor matched two provider arrays | Require exactly one match and widen the quota anchor to its owning declaration | Fresh Core and Management application | Resolved | -| PATCH-006 | P2 | Management overlay provenance | The reviewed replacement allowlist bound only upstream hashes, so local full-file replacement content could change without updating the review metadata | A customization-only edit could bypass the explicit replacement review boundary while the upstream file stayed unchanged | Modify a reviewed overlay replacement without changing its upstream counterpart | Move replacement provenance to a versioned manifest that binds the upstream tag and SHA-256 plus the overlay SHA-256, and reject changes on either side | Overlay collision source-hash test and repository manifest validation | Resolved | +| PATCH-006 | P2 | Management overlay provenance | The replacement manifest temporarily bound local overlay content to a checksum stored in the same commit | Intentional overlay edits required checksum churn without creating an independent trust boundary | Modify a reviewed overlay replacement without changing its upstream counterpart | Keep upstream source hashes and unexpected-collision rejection; review local overlay changes through the normal PR diff and behavior tests | Overlay collision tests and full Management validation | Resolved by simplification | | RELEASE-001 | P1 | GitHub Actions | Workflow actions used mutable major-version tags, including actions with release and workflow-run permissions | A moved tag could execute unreviewed code with repository or registry credentials | Inspect every external `uses:` reference | Pin all actions to commit SHAs and enable grouped Dependabot updates | Workflow pin checker and actionlint | Resolved | | RELEASE-002 | P1 | Release inputs | Matrix jobs resolved upstream tags and the models branch independently | One release could combine different source or model commits across platforms | Compare the checkout and models fetch inputs used by matrix jobs | Resolve Core, Management, and models commits once and pass immutable SHAs to every consumer | Workflow validation and release notes mapping | Resolved | | RELEASE-003 | P1 | Build dependencies | Container bases, the Komari executable image, manylinux builders, and downloaded Go archives were not content-verified | Rebuilding the same commit could execute different build or runtime inputs | Inspect Docker `FROM`/`COPY --from` and toolchain download steps | Pin base and build images, verify downloaded Go archives, and explicitly trust the official Komari `latest` image as a maintenance-first runtime dependency | Source-image build and workflow validation | Accepted upstream trust boundary | -| RELEASE-004 | P2 | Core build reproducibility | Rolling system repositories, hosted toolchains, and OCI exporters prevent a practical cross-time byte-for-byte guarantee | Rebuilding the same immutable source can receive newer system packages or exporter metadata, while official published artifacts remain authoritative | Rebuild after repository, runner-image, or exporter updates and compare artifact inputs and hashes | Keep immutable source inputs, deterministic build metadata, `-trimpath`, and normalized archives; deliberately allow rolling Debian packages and external toolchains | Reproducible archive tests, static workflow/Docker guards, and source-image build | Accepted maintenance boundary | +| RELEASE-004 | P2 | Core build reproducibility | Rolling system repositories, hosted toolchains, and OCI exporters prevent a practical cross-time byte-for-byte guarantee | Rebuilding the same immutable source can receive newer system packages or exporter metadata, while official published artifacts remain authoritative | Rebuild after repository, runner-image, or exporter updates and compare artifact inputs and hashes | Keep immutable source inputs, deterministic build metadata, `-trimpath`, and normalized archives; deliberately allow rolling Debian packages and external toolchains | Reproducible archive tests and source-image build | Accepted maintenance boundary | | STATE-001 | P1 | Runtime state queue | Failed SQLite writes were removed from the writer's pending maps, and snapshots were silently dropped when the channel filled | Routing cursors or account runtime statistics could lag permanently or disappear across export/restart | Force an auth-runtime insert failure or hold SQLite while queuing more than 1024 snapshots | Retain failed items, coalesce overflow by key, drain overflow before flush/delete/stop, and retry during shutdown | Runtime writer failure and overflow tests | Resolved | | STATE-002 | P1 | Usage collection lifecycle | `PopOldest` removed usage payloads before SQLite commit, and service shutdown closed the store without waiting for workers | A transient write error or graceful restart could lose an already-popped batch or race an in-flight store operation | Fail `usage_events` inserts after enqueue, then recover SQLite; cancel the service during background work | Keep the popped batch pending until commit, retry it, flush pending events on shutdown, and wait for workers before closing SQLite | Collector retry test and race detector | Resolved | | STATE-003 | P1 | Reset boundary | A delayed live batch could commit after reset and repopulate statistics with pre-reset requests | Reset appeared to succeed, then old requests reappeared and stale SSE cursors advanced | Reset after an event is queued or popped but before its insert transaction | Serialize live inserts with reset/retention and reject live events whose timestamps are at or before the persisted reset boundary | Live-event reset-boundary test and SSE reset tests | Resolved | @@ -70,7 +71,7 @@ The pinned PR baseline lives in `compatibility/upstream.env`. Pull requests and | FRONTEND-003 | P2 | Monitoring metadata and aggregates | Metadata and aggregate hooks did not include server identity in their request lifecycle | Equal cursors across servers or out-of-order refreshes could leave channels, auth metadata, rankings, or summaries from the previous connection | Switch between servers with the same latest usage ID while a refresh is in flight | Gate loads on authenticated connection identity, invalidate prior requests, clear cross-server data, and suppress stale responses | Fresh-management type, lint, and integration build validation | Resolved | | FRONTEND-004 | P2 | Account inspection transport | The inspection WebSocket was opened once with no close/error recovery or polling fallback | A transient proxy or network disconnect left progress and results stale until page remount or a manual action | Close the active WebSocket after the initial snapshot | Add capped exponential reconnect, five-second status polling while disconnected, request sequencing, and last-good snapshot preservation | WebSocket URL, credential transport, and reconnect-backoff tests | Resolved | | FRONTEND-005 | P2 | Routing runtime polling | Fifteen-second runtime polls could overlap and race with manual refresh, save, or release responses | An older runtime response could overwrite the latest protected-account/event state | Delay a poll response until after a release response | Prevent overlapping polls and apply only the latest request generation across load, poll, save, refresh, and release paths | Type checking, lint, and production build | Resolved | -| FRONTEND-006 | P2 | Monitoring maintainability | Monitoring, inspection, and routing pages had grown to roughly 6.4k, 3.6k, and 1k lines, and the monitoring page still owned analytics, account quota adaptation, settings/price modals, realtime presentation, and a 6.2k-line page stylesheet | Further realtime, pricing, and account-view changes were difficult to test independently and increased regression risk | Inspect page/component sizes, repeated helper ownership, and page-to-feature dependency direction | Extract monitoring analytics/aggregates, account quota/health, realtime preferences/presentation, controlled settings/price components, account panels, inspection view models/reducers, and ordered Sass partials; make pages consume those contracts and guard against reabsorption | Focused Bun tests, source/style-boundary tests, and full Management validation | Resolved | +| FRONTEND-006 | P2 | Monitoring maintainability | Monitoring, inspection, and routing pages had grown to roughly 6.4k, 3.6k, and 1k lines, and the monitoring page still owned analytics, account quota adaptation, settings/price modals, realtime presentation, and a 6.2k-line page stylesheet | Further realtime, pricing, and account-view changes were difficult to test independently and increased regression risk | Inspect page/component sizes, repeated helper ownership, and page-to-feature dependency direction | Extract monitoring analytics/aggregates, account quota/health, realtime preferences/presentation, controlled settings/price components, account panels, inspection view models/reducers, and ordered Sass partials | Focused Bun tests and full Management validation | Resolved | | LIFECYCLE-001 | P2 | Management handler lifecycle | Account-inspection, routing reconciliation, login-attempt cleanup, named usage registration, and embedded-usage callbacks outlived a stopped server | Embedders that repeatedly constructed and stopped servers retained goroutines and handler references | Construct and stop management handlers repeatedly in one process | Give each handler a cancellable lifecycle, wait for scheduled and active work, remove global registrations, and invoke shutdown from `Server.Stop` | Handler shutdown, named-plugin ownership, full Core tests, and race detector | Resolved | | FRONTEND-007 | P2 | API request cancellation | Connection generations rejected stale Axios responses but did not abort the underlying requests | Superseded requests continued consuming client and server resources until response or timeout | Hold an adapter request open and switch server credentials | Abort the previous connection controller and combine its signal with any caller-provided cancellation signal | Physical connection-abort and caller-cancellation tests | Resolved | | FRONTEND-008 | P2 | Inspection reconnect detail recovery | WebSocket downtime used summary polling, so missed individual log entries were not refreshed immediately when the socket reconnected | Operators could briefly see an incomplete log page after transport recovery | Drop the inspection socket after a live log and reconnect it | Refresh the current filtered result/log detail page immediately on socket open while retaining the last good snapshot on failure | Management transport tests, type checking, lint, and production build | Resolved | @@ -82,6 +83,18 @@ The pinned PR baseline lives in `compatibility/upstream.env`. Pull requests and - `RELEASE-003` owner: project maintainers. The official Komari `latest` image is trusted as an upstream runtime component so updates arrive without digest-maintenance PRs; pinned base/build images and verified Go archives remain in place. - `RELEASE-004` owner: project maintainers. Rolling Debian security packages and hosted toolchains are accepted because lower maintenance and timely system updates matter more than cross-time byte-for-byte reproduction; deterministic source identity, build metadata, and release archives remain enforced. +## Package 13 Simplification Results + +- Daily full latest-upstream CI was removed because the Core and Management release workflows already detect new upstream releases and run the same validation before publishing. Pull requests, pushes to `main`, and manual pinned/latest validation remain available; manual latest Core validation still enables the race detector. +- The overlay replacement manifest now binds only reviewed upstream source hashes. Local overlay content remains visible in normal pull-request diffs and covered by behavior tests, while new collisions and changed upstream replacements still fail before writes. +- Page line limits, exact Sass-partial counts, extracted-symbol source scans, and workflow/Docker string-count policies were retired. Focused Bun tests, archive determinism tests, fresh-upstream validation, source Docker builds, and release gates remain. + +## Package 13 Validation Results + +- Repository validation: 19 focused Management customization/security tests and two reproducible archive tests passed, together with workflow action pin, syntax, JSON, and whitespace validation. +- Management `v1.18.5`: upstream replacement preflight, repeated-application invariance, 110 Bun tests, ESLint with no warnings, TypeScript checking, and the production single-file build passed on a fresh checkout. +- Core `v7.2.94`: guarded-source preflight, rejected reapplication invariance, embedded usage tests, management handlers, plugin host/store, Redis queue, auth scheduler tests, and the server build passed on a fresh checkout. + ## Package 12 Residual Risks - `AccountInspectionPage.tsx` is now 2211 lines, down from 3493 (about 37%). It intentionally remains the page-level coordinator for transport lifecycle, destructive-action commands, settings persistence, and rendered composition; pure result derivation, account statistics, confirmation builders, and reducer state now live in the feature model. @@ -90,7 +103,7 @@ The pinned PR baseline lives in `compatibility/upstream.env`. Pull requests and ## Package 12 Validation Results -- Repository validation: 25 Management customization tests and two reproducible archive tests passed. Boundary guards keep the account-inspection page below 2300 lines, prevent extracted model/reducer implementations from returning to it, and enforce feature ownership of its model and styles. +- Repository validation at completion included 25 Management customization tests and two reproducible archive tests. The implementation-shape guards were retired in Package 13 after focused behavior coverage was established. - Management `v1.18.5`: bidirectional overlay replacement preflight, repeated-application invariance, 110 Bun tests, ESLint with no warnings, TypeScript checking, and the `v1.18.5-pro` production single-file build passed on a fresh checkout. - Core `v7.2.94`: guarded-source preflight, rejected reapplication invariance, embedded usage tests, management handlers, plugin host/store, Redis queue, auth scheduler tests, and the server build passed on a fresh checkout. - Focused coverage verifies one-pass inspection-result classification, pending-action counts, pagination boundaries, and deterministic settings-draft conversion. @@ -105,7 +118,7 @@ The pinned PR baseline lives in `compatibility/upstream.env`. Pull requests and ## Package 11 Validation Results -- Repository validation: 23 Management customization tests and two reproducible archive tests passed. Boundary guards keep the monitoring page below 2300 lines, prevent account/settings/price implementations from returning to it, and enforce feature ownership of monitoring styles. +- Repository validation at completion included 23 Management customization tests and two reproducible archive tests. The implementation-shape guards were retired in Package 13 after focused behavior coverage was established. - Management `v1.18.5`: bidirectional overlay replacement preflight, repeated-application invariance, 108 Bun tests, ESLint with no warnings, TypeScript checking, and the production single-file build passed on a fresh checkout after the Sass split. - Core `v7.2.94`: guarded-source preflight, rejected reapplication invariance, embedded usage tests, management handlers, plugin host/store, Redis queue, auth scheduler tests, and the server build passed on a fresh checkout. - Focused coverage verifies provider-scoped account-quota targeting/de-duplication, monitoring-settings defaults and normalization, and model-price draft/rate behavior. @@ -120,7 +133,7 @@ The pinned PR baseline lives in `compatibility/upstream.env`. Pull requests and ## Package 10 Validation Results -- Repository validation: 22 Management customization tests and two reproducible archive tests passed, including a source guard that keeps `MonitoringCenterPage.tsx` below 4000 lines and prevents extracted analytics, realtime, preference, cost, and health implementations from returning to the page. Local ShellCheck and actionlint executables were unavailable; required PR checks provide those gates. +- Repository validation at completion included 22 Management customization tests and two reproducible archive tests. The temporary source-shape guard was retired in Package 13; focused frontend tests and required PR checks remain. - Management `v1.18.5`: bidirectional overlay replacement preflight, repeated-application invariance, 102 Bun tests, ESLint with no warnings, TypeScript checking, and the production single-file build passed on a fresh checkout. - Core `v7.2.94`: guarded-source preflight, rejected reapplication invariance, embedded usage tests, management handlers, plugin host/store, Redis queue, auth scheduler tests, and the server build passed on a fresh checkout. - Focused frontend coverage verifies aggregate summaries/account grouping, fallback analytics scoping, account-health buckets, realtime error classification/enrichment/pagination, and persisted realtime-column migration/width constraints. @@ -133,7 +146,7 @@ The pinned PR baseline lives in `compatibility/upstream.env`. Pull requests and - Base and manylinux images remain pinned, while Debian packages and the official Komari `latest` image intentionally follow upstream updates. This reduces pin maintenance but means rebuilding an unchanged commit at a later date can consume different runtime inputs. - Docker/BuildKit layer tar metadata remains exporter-dependent. Consumers must verify the digest published for each release rather than assume a locally rebuilt OCI digest will match byte-for-byte. - Local Docker builds that intentionally resolve `latest` and omit `CLIPROXY_COMMIT` or `SOURCE_DATE_EPOCH` remain dynamic. Deterministic source binaries require the documented immutable commit and epoch inputs used by CI/release paths. -- Exact-string patch anchors and reviewed full-file replacements remain manual merge boundaries when upstream changes. Both now fail closed before writes; the manifest binds changes on either side, but semantic reconciliation still requires human review. +- Exact-string patch anchors and reviewed full-file replacements remain manual merge boundaries when upstream changes. Both fail closed before writes; the manifest binds reviewed upstream replacements, while local overlay changes use normal PR review and behavior validation. ## Package 9 Validation Results @@ -141,7 +154,7 @@ The pinned PR baseline lives in `compatibility/upstream.env`. Pull requests and - The source Docker build for pinned Core `v7.2.94` and commit `36b45d57a3e804b9dfcee307e5d7b3e8cea5acfc` completed through both builder and runtime apt installs and produced the final image with a fixed build date and `-trimpath`. - Core `v7.2.94`: guarded-source preflight, rejected reapplication invariance, embedded usage tests, management handlers, plugin host/store, Redis queue, auth scheduler tests, the embedded usage and targeted package race detector, and the server build passed on a fresh generated checkout. - Management `v1.18.5`: bidirectional overlay replacement preflight, repeated-application invariance, 91 Bun tests, ESLint with no warnings, TypeScript checking, and the production single-file build passed on a fresh checkout. -- Regression coverage verifies deterministic tar.gz/zip ordering and metadata, repository-wide use of fixed Core build inputs, the accepted Docker maintenance policy, manifest baseline alignment, and rejection when either a reviewed upstream replacement or its overlay source changes without a manifest update. +- Regression coverage verifies deterministic tar.gz/zip ordering and metadata, repository-wide fixed Core build inputs, and rejection when a reviewed upstream replacement changes. ## Package 8 Residual Risks @@ -219,7 +232,7 @@ The pinned PR baseline lives in `compatibility/upstream.env`. Pull requests and ## Package 2 Residual Risks -- Some patch operations still depend on exact upstream source blocks. They now fail on missing or non-unique anchors, and scheduled latest-upstream validation prevents silent release drift. +- Some patch operations still depend on exact upstream source blocks. They fail on missing or non-unique anchors, while manual latest-upstream validation and automatic release gates prevent silent release drift. ## Package 2 Validation Results @@ -233,13 +246,13 @@ The pinned PR baseline lives in `compatibility/upstream.env`. Pull requests and - Repository validation: 14 Python customization tests, Python and shell syntax, JSON parsing, ShellCheck, actionlint, and whitespace checks passed. - Core `v7.2.94`: embedded usage, management handlers, plugin host/store, Redis queue, auth scheduler tests, and the server build passed on a fresh checkout. - Management `v1.18.5`: 75 Bun tests, ESLint, TypeScript checking, and the single-file production build passed on a fresh checkout. -- Scheduled latest-upstream Core validation enables the Go race detector; deterministic PR and release validation use the same package set without `-race`. +- Manual latest-upstream Core validation enables the Go race detector; deterministic PR and release validation use the same package set without `-race`. ## Completion Criteria - Required PR checks cover repository, Core, and Management validation. - Release workflows cannot publish before the same Core and Management validation succeeds. -- Scheduled latest-upstream checks report patch drift independently of the pinned PR baseline. +- Manual latest-upstream validation and automatic release gates report patch drift independently of the pinned PR baseline. - All P0 and P1 findings are fixed or explicitly accepted with an owner and rationale. - All P2 findings are resolved, mitigated with explicit bounds, or recorded as deliberate compatibility boundaries with a retirement trigger. - Each behavioral fix includes a regression test and a fresh-upstream validation result. diff --git a/scripts/validation/check_reproducible_builds.py b/scripts/validation/check_reproducible_builds.py deleted file mode 100644 index 0c97746..0000000 --- a/scripts/validation/check_reproducible_builds.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python3 -import json -import sys -from pathlib import Path - - -def require(text: str, needle: str, source: Path) -> None: - if needle not in text: - raise SystemExit(f'{source}: missing reproducible-build guard: {needle}') - - -def check_dockerfile_maintenance_policy(path: Path) -> None: - text = path.read_text() - for forbidden in ('DEBIAN_SNAPSHOT', 'snapshot.debian.org', 'komari-agent:latest@sha256:'): - if forbidden in text: - raise SystemExit(f'{path}: retired high-maintenance Docker pin remains: {forbidden}') - - -def main() -> None: - if len(sys.argv) != 2: - raise SystemExit(f'Usage: {sys.argv[0]} /path/to/repository') - root = Path(sys.argv[1]).resolve() - compatibility = dict( - line.split('=', 1) - for line in (root / 'compatibility/upstream.env').read_text().splitlines() - if line and not line.startswith('#') and '=' in line - ) - overlay_manifest_path = root / 'cliproxyapi-pro-management/overlay-replacements.json' - overlay_manifest = json.loads(overlay_manifest_path.read_text()) - if overlay_manifest.get('upstream', {}).get('tag') != compatibility.get('MANAGEMENT_UPSTREAM_TAG'): - raise SystemExit(f'{overlay_manifest_path}: upstream tag must match compatibility/upstream.env') - check_dockerfile_maintenance_policy(root / 'cliproxyapi-pro-core/Dockerfile') - check_dockerfile_maintenance_policy(root / 'cliproxyapi-pro-core/Dockerfile.runtime') - - ci_path = root / '.github/workflows/ci.yml' - ci = ci_path.read_text() - require(ci, 'core_source_date_epoch:', ci_path) - require(ci, '--build-arg "SOURCE_DATE_EPOCH=${source_date_epoch}"', ci_path) - - release_path = root / '.github/workflows/release-core.yml' - release = release_path.read_text() - require(release, 'source_date_epoch=${source_date_epoch}', release_path) - require(release, 'GIT_AUTHOR_DATE="${build_date}" GIT_COMMITTER_DATE="${build_date}"', release_path) - if release.count('create_reproducible_archive.py') != 4: - raise SystemExit(f'{release_path}: every core archive path must use the reproducible archive helper') - if release.count('-trimpath') < 4: - raise SystemExit(f'{release_path}: every core build path must trim source paths') - for forbidden in ('Compress-Archive', 'tar -C "${archive_dir}" -czf', 'BUILD_DATE="$(date'): - if forbidden in release: - raise SystemExit(f'{release_path}: non-reproducible release command remains: {forbidden}') - - -if __name__ == '__main__': - main() diff --git a/scripts/validation/repo.sh b/scripts/validation/repo.sh index a463c75..0ade068 100755 --- a/scripts/validation/repo.sh +++ b/scripts/validation/repo.sh @@ -11,7 +11,6 @@ python3 -m py_compile \ "${repo_root}/cliproxyapi-pro-core/patches/apply_upstream_patches.py" \ "${repo_root}/cliproxyapi-pro-management/apply_customizations.py" \ "${repo_root}/scripts/validation/check_workflow_actions.py" \ - "${repo_root}/scripts/validation/check_reproducible_builds.py" \ "${repo_root}/scripts/build/create_reproducible_archive.py" python3 -m unittest discover \ @@ -31,8 +30,6 @@ python3 -m json.tool \ python3 "${repo_root}/scripts/validation/check_workflow_actions.py" \ "${repo_root}/.github/workflows" -python3 "${repo_root}/scripts/validation/check_reproducible_builds.py" \ - "${repo_root}" sh -n "${repo_root}/cliproxyapi-pro-core/entrypoint.sh" bash -n \