Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions Scripts/check_caam_observability_locales.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""Check feature literal-key coverage without requiring Apple's plutil.

This bounded source/catalog check does not replace native strings parsing or Swift tests.
The explicit --sync-locales maintenance mode adds only missing keys, with labeled English
fallbacks; it never claims to translate text or changes existing translations.
"""

from __future__ import annotations

import argparse
from pathlib import Path
import re


ROOT = Path(__file__).resolve().parents[1]
RESOURCES = ROOT / "Sources/CodexBar/Resources"
SOURCE_NAMES = (
"CAAMEnvironmentControlsView.swift",
"CAAMEnvironmentCoordinator.swift",
"CodexObservabilityHubView.swift",
"PreferencesCodexEnvironmentsSection.swift",
"PreferencesProvidersPane.swift",
)
LOCALES = (
"en", "ar", "ca", "de", "es", "fa", "fr", "gl", "id", "it", "ja", "ko", "nl",
"pl", "pt-BR", "ru", "sv", "th", "tr", "uk", "vi", "zh-Hans", "zh-Hant",
)
LITERAL = re.compile(r'\bL\(\s*"((?:[^"\\]|\\.)*)"\s*\)')
ENTRY = re.compile(r'^"((?:[^"\\]|\\.)*)"\s*=\s*"((?:[^"\\]|\\.)*)";', re.MULTILINE)
PLACEHOLDER = re.compile(r'%(?:(\d+)\$)?[-+ #0]*\d*(?:\.\d+)?(?:hh|ll|[hlLzjt])?[@diuoxXfFeEgGaAcsp]')


def bounded_read(path: Path) -> str:
if not path.resolve().is_relative_to(ROOT) or path.stat().st_size > 1024 * 1024:
raise ValueError("Source or catalog is outside the repository or exceeds the size limit")
return path.read_text(encoding="utf-8")


def signature(value: str) -> list[str]:
value = value.replace("%%", "")
return sorted(re.sub(r'^%\d+\$', "%", match.group(0)) for match in PLACEHOLDER.finditer(value))


def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--sync-locales", action="store_true", help="append explicitly labeled English fallback keys")
options = parser.parse_args()
keys: set[str] = set()
for name in SOURCE_NAMES:
keys.update(LITERAL.findall(bounded_read(ROOT / "Sources/CodexBar" / name)))
english_path = RESOURCES / "en.lproj/Localizable.strings"
english = dict(ENTRY.findall(bounded_read(english_path)))
new_keys = sorted(keys - english.keys())
if options.sync_locales:
for locale in LOCALES:
path = RESOURCES / f"{locale}.lproj/Localizable.strings"
text = bounded_read(path)
existing = dict(ENTRY.findall(text))
# Reconcile every feature key so interruption after the English write is restart-safe.
missing = sorted(keys - existing.keys())
if missing:
addition = "\n/* CAAM observability: English fallback pending translation review. */\n"
addition += "".join(f'"{key}" = "{english.get(key, key)}";\n' for key in missing)
path.write_text(text.rstrip() + "\n" + addition, encoding="utf-8")
print(f"Registered {len(new_keys)} new English keys across {len(LOCALES)} catalogs (not translations).")
english = dict(ENTRY.findall(bounded_read(english_path)))
failures: list[str] = []
for locale in LOCALES:
entries = dict(ENTRY.findall(bounded_read(RESOURCES / f"{locale}.lproj/Localizable.strings")))
for key in sorted(keys):
value = entries.get(key)
if value is None:
failures.append(f"{locale}: missing {key}")
elif signature(value) != signature(english.get(key, key)):
failures.append(f"{locale}: placeholder mismatch {key}")
if failures:
print("\n".join(failures[:30]))
print(f"FAILED: {len(failures)} coverage/placeholder issues.")
return 1
print(f"PASS: {len(keys)} feature literal keys covered in {len(LOCALES)} catalogs; placeholders match.")
print("Native plutil parsing, linguistic review, and Swift execution are separate checks.")
return 0


if __name__ == "__main__":
raise SystemExit(main())
67 changes: 67 additions & 0 deletions Scripts/test_check_caam_observability_locales.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Portable fixture tests for the feature's catalog maintenance helper, not Swift behavior."""

import contextlib
import io
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch

import check_caam_observability_locales as checker


class CatalogHelperTests(unittest.TestCase):
def test_literal_keys_include_multiline_calls_and_escaped_quotes(self):
text = 'L("One")\nL(\n "Two"\n)\nL("A \\"quote\\"")\nL(variable)'
self.assertEqual(checker.LITERAL.findall(text), ["One", "Two", 'A \\"quote\\"'])

def test_placeholder_positions_normalize_without_counting_literal_percent(self):
self.assertEqual(checker.signature("%2$d · %1$@ · 100%%"), checker.signature("%@ · %d"))
self.assertNotEqual(checker.signature("%.1f"), checker.signature("%d"))

def test_bounded_read_rejects_outside_root(self):
with tempfile.TemporaryDirectory() as directory:
outside = Path(directory) / "outside"
outside.write_text("fixture", encoding="utf-8")
with self.assertRaises(ValueError):
checker.bounded_read(outside)

def test_bounded_read_rejects_oversized_file(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
fixture = root / "large"
fixture.write_bytes(b"x" * (1024 * 1024 + 1))
with patch.object(checker, "ROOT", root), self.assertRaises(ValueError):
checker.bounded_read(fixture)

def test_sync_recovers_after_english_written_and_preserves_translation(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
resources = root / "Sources/CodexBar/Resources"
for locale in ("en", "de"):
(resources / f"{locale}.lproj").mkdir(parents=True)
source = root / "Sources/CodexBar/Fixture.swift"
source.write_text('L("Existing")\nL("Missing %@")', encoding="utf-8")
english = resources / "en.lproj/Localizable.strings"
german = resources / "de.lproj/Localizable.strings"
english.write_text('"Existing" = "Existing";\n"Missing %@" = "Missing %@";\n', encoding="utf-8")
german.write_text('"Existing" = "Vorhanden";\n', encoding="utf-8")
with (
patch.object(checker, "ROOT", root),
patch.object(checker, "RESOURCES", resources),
patch.object(checker, "SOURCE_NAMES", ("Fixture.swift",)),
patch.object(checker, "LOCALES", ("en", "de")),
patch("sys.argv", ["check", "--sync-locales"]),
contextlib.redirect_stdout(io.StringIO()),
):
self.assertEqual(checker.main(), 0)
first = german.read_bytes()
self.assertEqual(checker.main(), 0)
self.assertEqual(german.read_bytes(), first)
self.assertIn('"Existing" = "Vorhanden";', first.decode("utf-8"))
self.assertIn('"Missing %@" = "Missing %@";', first.decode("utf-8"))


if __name__ == "__main__":
unittest.main()
146 changes: 146 additions & 0 deletions Sources/CodexBar/CAAMEnvironmentControlsView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import CodexBarCore
import SwiftUI

@MainActor
struct CAAMEnvironmentControlsView: View {
let configuration: CAAMEnvironmentConfiguration
@Bindable var coordinator: CAAMEnvironmentCoordinator

private var session: CAAMControlSession {
self.coordinator.session(for: self.configuration)
}

var body: some View {
let session = self.session
let row = session.row(now: Date())
VStack(alignment: .leading, spacing: 6) {
HStack {
Menu(L("Plan switch")) {
ForEach(session.snapshot?.profiles.filter { $0.eligible && !$0.active } ?? []) { profile in
Button(profile.name) {
Task {
await self.coordinator.plan(configuration: self.configuration, target: profile.name)
}
}
}
}
.disabled(!row.canSwitch || self.coordinator.isRefreshing || session.phase.isBusy)
Button(L("Plan fallback")) {
guard let target = session.snapshot?.fallbackProfile else { return }
Task {
await self.coordinator.plan(configuration: self.configuration, target: target, fallback: true)
}
}
.disabled(!row.canRestoreFallback || self.coordinator.isRefreshing || session.phase.isBusy)
if session.pendingOperationID != nil {
Button(L("Look up operation")) {
Task { await self.coordinator.lookup(configuration: self.configuration) }
}
.disabled(session.phase.isBusy || self.coordinator.isRefreshing)
Button(L("Review recovery")) {
self.coordinator.prepareRecovery(configuration: self.configuration)
}
.disabled(!row.canRecover || session.phase.isBusy || self.coordinator.isRefreshing)
}
}
.buttonStyle(.bordered)
.controlSize(.small)
.disabled(self.coordinator.isOperating)

if session.phase.isBusy {
HStack {
ProgressView().controlSize(.small)
Text(L("Waiting for CAAM; the app remains available."))
}
.font(.caption)
}
if let operationID = session.pendingOperationID {
Text(String(format: L("Unresolved operation: %@"), operationID.uuidString.lowercased()))
.font(.caption2.monospaced())
.textSelection(.enabled)
}
if session.phase == .unknown {
Text(L("Operation outcome is unresolved. Look up the existing operation before another change."))
.font(.caption).foregroundStyle(.orange)
}
if session.phase == .manualRequired {
Text(L("CAAM requires manual intervention on this environment. Automatic recovery is disabled."))
.font(.caption).foregroundStyle(.orange)
}
if let failure = session.failure {
Text(failure.localizedDescription).font(.caption).foregroundStyle(.orange)
if let code = failure.code {
Text(String(format: L("Gateway code: %@"), code)).font(.caption2.monospaced())
}
}
if let result = session.lastResult {
Text(String(format: L("Last operation: %@ · %@"), result.state.rawValue, result.effect.rawValue))
.font(.caption)
}
if let confirmation = session.confirmation {
self.confirmationView(confirmation)
} else if !self.coordinator.mutationsQualified(for: self.configuration) {
Text(L("Read-only until CAAM conformance is independently qualified."))
.font(.caption)
.foregroundStyle(.secondary)
}
}
}

private func confirmationView(_ confirmation: CAAMControlConfirmation) -> some View {
GroupBox {
VStack(alignment: .leading, spacing: 6) {
Text(self.confirmationTitle(confirmation.kind)).font(.caption.weight(.semibold))
Text(String(format: L("Expected revision: %@"), confirmation.revision)).font(.caption.monospaced())
if let plan = confirmation.plan {
Text(String(
format: L("Change host default: %@ → %@"),
plan.currentProfile ?? "—",
plan.targetProfile))
Text(String(format: L("Plan digest: %@"), plan.planDigest)).font(.caption2.monospaced())
Text(plan.expiresAt, style: .time).font(.caption)
DisclosureGroup(L("Affected profiles")) {
ScrollView {
Text(plan.affectedProfiles.joined(separator: ", "))
.font(.caption).frame(maxWidth: .infinity, alignment: .leading)
}
.frame(maxHeight: 100)
}
if plan.reloadRequired {
Text(L("A running Codex process may need to reload.")).font(.caption)
}
} else {
Text(L("CAAM may complete or roll back its pending transaction. This does not restore fallback."))
.font(.caption)
}
HStack {
Button(L("Confirm once")) {
Task {
await self.coordinator.confirm(
configuration: self.configuration,
confirmationID: confirmation.id)
}
}
.disabled(!self.coordinator.mutationsQualified(for: self.configuration) ||
self.coordinator.isOperating || self.coordinator.isRefreshing)
Button(L("Cancel")) { self.coordinator.cancelConfirmation(configuration: self.configuration) }
}
.buttonStyle(.bordered)
.controlSize(.small)
if !self.coordinator.mutationsQualified(for: self.configuration) {
Text(L("Read-only until CAAM conformance is independently qualified."))
.font(.caption).foregroundStyle(.secondary)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}

private func confirmationTitle(_ kind: CAAMControlConfirmation.Kind) -> String {
switch kind {
case .switchProfile: L("Review switch plan")
case .restoreFallback: L("Review fallback restoration")
case .recoverSwitch: L("Review interrupted-operation recovery")
}
}
}
Loading
Loading