From 20d3dd0e49f860eb82020e77f21ea0c0aae8c6bb Mon Sep 17 00:00:00 2001 From: Fernando Fernandes Date: Sun, 13 Sep 2026 18:31:57 +0200 Subject: [PATCH 1/3] Update agent guidelines to 0.0.31 --- AgentGuidelines/CHANGELOG.md | 6 ++++ AgentGuidelines/Guidelines/CICD.md | 33 +++++++++++++++++++ AgentGuidelines/README.md | 4 +-- .../Scripts/validate_guidelines.swift | 10 ++++++ AgentGuidelines/Tests/run_tests.swift | 21 ++++++++++++ AgentGuidelines/VERSION | 2 +- 6 files changed, 73 insertions(+), 3 deletions(-) diff --git a/AgentGuidelines/CHANGELOG.md b/AgentGuidelines/CHANGELOG.md index 6feaeb1..bd00f0c 100644 --- a/AgentGuidelines/CHANGELOG.md +++ b/AgentGuidelines/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project are documented in this file. +## [0.0.31] - 2026-09-13 + +### Added + +- Added a least-privilege GitHub App authentication pattern for workflows that resolve private sibling repositories, including short-lived read-only tokens, process-scoped Git configuration, exact repository selection, and fork pull-request and self-hosted-runner security boundaries. + ## [0.0.30] - 2026-09-12 ### Changed diff --git a/AgentGuidelines/Guidelines/CICD.md b/AgentGuidelines/Guidelines/CICD.md index 190739d..5138aca 100644 --- a/AgentGuidelines/Guidelines/CICD.md +++ b/AgentGuidelines/Guidelines/CICD.md @@ -20,6 +20,39 @@ Use Swift for new repository-owned executable scripts in Swift-focused applicati Fall back to Python or a POSIX shell script only when the required behavior cannot be implemented with the repository's supported Swift toolchain and Foundation APIs. Document the exception in durable repository documentation in the same change, including the missing Swift capability, exact script and task scope, runtime and dependency requirements, security and maintenance impact, validation method, and condition for revisiting or removing the exception. Keep the fallback narrow; an existing non-Swift script does not authorize another one. The central `Scripts/swift_format.sh` command wrapper is the retained documented exception for invoking Xcode's `swift-format` modes. +## Private repository dependencies + +The workflow repository's `GITHUB_TOKEN` does not grant access to private dependencies in sibling repositories. When Swift Package Manager or another build tool must clone private ThatFactory repositories, use a GitHub App installed on every required dependency repository. The app does not need access to the workflow repository unless that repository is also an intended token target. Grant the app only read access to repository contents, mint a short-lived installation token with `actions/create-github-app-token`, and list the exact dependency repositories in the action's `repositories` input. Do not use a personal access token, a long-lived machine credential, or an organization-wide token when the GitHub App can provide the required scope. + +Expose the installation token only to steps that resolve or build the private dependencies. Supply HTTPS authentication through Git's process-level `GIT_CONFIG_COUNT`, `GIT_CONFIG_KEY_0`, and `GIT_CONFIG_VALUE_0` environment variables so the credential is not persisted in repository or global Git configuration. Keep the existing dependency URLs as `https://github.com//` URLs. For example: + +```yaml +- name: Create private dependency token + id: private-dependencies + uses: actions/create-github-app-token@v3 + with: + client-id: ${{ vars.PRIVATE_DEPENDENCIES_APP_CLIENT_ID }} + private-key: ${{ secrets.PRIVATE_DEPENDENCIES_APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: | + first-private-package + second-private-package + permission-contents: read + +- name: Test + env: + GIT_CONFIG_COUNT: 1 + GIT_CONFIG_KEY_0: url.https://x-access-token:${{ steps.private-dependencies.outputs.token }}@github.com/.insteadOf + GIT_CONFIG_VALUE_0: https://github.com/ + run: swift test +``` + +The GitHub App's installation and repository selection are part of the security boundary. Consumer documentation must name the app variable and secret, list the private repositories the workflow requires, record the required `Contents: read` permission, and identify the jobs or steps that receive the token. Keep pull-request and protected-branch workflows consistent unless a documented trust boundary requires otherwise. Because `GIT_CONFIG_*` values are ordinary inherited environment variables, treat the credential-bearing resolve or build step and its complete subprocess tree as privileged. Tests, build scripts, SwiftPM plugins, and other code executed beneath that step must be trusted to receive read access to every repository in the token scope. + +Repository secrets are unavailable to workflows triggered by pull requests from forks. A repository that accepts fork-originated or otherwise untrusted pull requests must keep a secretless validation path or deliberately skip private-dependency jobs with an explicit, documented condition. Untrusted code must not execute on a persistent self-hosted runner that is later reused for credential-bearing work. Use an isolated disposable or ephemeral self-hosted runner, an appropriate GitHub-hosted runner where possible, or a separate runner pool or host that never subsequently receives secrets; otherwise skip the untrusted validation. + +This trust rule is event-independent. Do not combine credentials with code that is not trusted at that privilege level under `pull_request`, `pull_request_target`, `issue_comment`, `workflow_run`, or another trigger. On self-hosted runners, do not expose the token to unrelated steps, caches, artifacts, logs, or persistent configuration; retain the action's default post-job token revocation. + ## `ci-pr.yml` Projects using GitHub Actions should keep pull-request validation in `.github/workflows/ci-pr.yml`, triggered by `pull_request` events for `opened`, `synchronize`, and `reopened`. diff --git a/AgentGuidelines/README.md b/AgentGuidelines/README.md index 821a309..0c684f2 100644 --- a/AgentGuidelines/README.md +++ b/AgentGuidelines/README.md @@ -91,7 +91,7 @@ From the consumer repository root, install a tagged release: git subtree add \ --prefix=AgentGuidelines \ https://github.com/thatfactory/agent-guidelines.git \ - 0.0.30 \ + 0.0.31 \ --squash ``` @@ -147,7 +147,7 @@ Review the target release's changelog, then pull it deliberately: git subtree pull \ --prefix=AgentGuidelines \ https://github.com/thatfactory/agent-guidelines.git \ - 0.0.30 \ + 0.0.31 \ --squash ``` diff --git a/AgentGuidelines/Scripts/validate_guidelines.swift b/AgentGuidelines/Scripts/validate_guidelines.swift index 3139e94..655eadd 100755 --- a/AgentGuidelines/Scripts/validate_guidelines.swift +++ b/AgentGuidelines/Scripts/validate_guidelines.swift @@ -579,11 +579,21 @@ func validateExternalDependencyPolicy(_ errors: inout [String]) { if let contents = readText(cicdGuideline, errors: &errors) { let required = [ "## Tooling and automation": "CI/CD tooling policy section", + "## Private repository dependencies": "private repository dependency authentication section", "Fastlane is forbidden": "forbidden delivery tooling", "xcode-cloud-mcp": "first-party Xcode Cloud tooling", "app-store-connect-mcp": "first-party App Store tooling", "required behavior cannot be implemented": "non-Swift capability-gap threshold", "missing Swift capability": "documented non-Swift exception", + "actions/create-github-app-token@v3": "short-lived GitHub App token workflow", + "client-id:": "current GitHub App client identifier input", + "permission-contents: read": "read-only private dependency permission", + "GIT_CONFIG_KEY_0": "process-level Git authentication", + "GIT_CONFIG_VALUE_0: https://github.com/": "GitHub HTTPS rewrite source", + "complete subprocess tree as privileged": "credential-bearing subprocess trust boundary", + "isolated disposable or ephemeral self-hosted runner": "untrusted-code runner isolation", + "This trust rule is event-independent": "event-independent credential boundary", + "pull_request_target": "untrusted pull-request credential boundary", ] for (value, description) in required where !contents.contains(value) { errors.append("Guidelines/CICD.md: missing \(description): '\(value)'") diff --git a/AgentGuidelines/Tests/run_tests.swift b/AgentGuidelines/Tests/run_tests.swift index 516c7c8..3d61c70 100755 --- a/AgentGuidelines/Tests/run_tests.swift +++ b/AgentGuidelines/Tests/run_tests.swift @@ -146,6 +146,27 @@ let tests: [(String, () throws -> Void)] = [ } } ), + ( + "repository validator rejects private dependency authentication drift", + { + try withTemporaryDirectory { temporary in + let fixture = temporary.appendingPathComponent("repository") + try copyRepositoryFixture(to: fixture) + let guideline = fixture.appendingPathComponent("Guidelines/CICD.md") + var contents = try String(contentsOf: guideline, encoding: .utf8) + contents = contents.replacingOccurrences( + of: "isolated disposable or ephemeral self-hosted runner", + with: "self-hosted runner") + try write(contents, to: guideline) + let result = try run([fixture.appendingPathComponent("Scripts/validate_guidelines.swift").path]) + try require(!result.succeeded, "private dependency authentication drift unexpectedly passed") + try require( + result.output.contains("missing untrusted-code runner isolation"), + result.output + ) + } + } + ), ( "repository validator rejects gitignore template drift", { diff --git a/AgentGuidelines/VERSION b/AgentGuidelines/VERSION index f092e2b..d788d43 100644 --- a/AgentGuidelines/VERSION +++ b/AgentGuidelines/VERSION @@ -1 +1 @@ -0.0.30 +0.0.31 From 6e6e82c27957a88275bfbff040d187f488c1c544 Mon Sep 17 00:00:00 2001 From: Fernando Fernandes Date: Sun, 13 Sep 2026 18:38:37 +0200 Subject: [PATCH 2/3] Add recognition lifecycle logging --- AGENTS.md | 1 + CHANGELOG.md | 6 +++ Package.swift | 5 +- README.md | 2 + Sources/TextCaptureKit/TextCaptureLog.swift | 52 +++++++++++++++++++ .../TextCaptureRecognizer.swift | 8 +++ .../TextCaptureLogTests.swift | 46 ++++++++++++++++ 7 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 Sources/TextCaptureKit/TextCaptureLog.swift create mode 100644 Tests/TextCaptureKitTests/TextCaptureLogTests.swift diff --git a/AGENTS.md b/AGENTS.md index 661f814..2c9fc7c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -111,3 +111,4 @@ Replace these examples with exact repository paths: - Use the Vision framework for recognition; VisionKit acquisition and scanner UI remain host responsibilities. - Keep recognition asynchronous, results `Sendable`, and reading-order behavior deterministic. +- TextCaptureKit's canonical logging emoji is 👁️. diff --git a/CHANGELOG.md b/CHANGELOG.md index d143bab..779cf34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to TextCaptureKit are documented here. +## Unreleased + +### Added + +- Added privacy-safe, emoji-prefixed recognition outcome logs through AppLogger. + ## 0.1.0 - 2026-09-12 ### Added diff --git a/Package.swift b/Package.swift index bc9fbe6..cb6bc4f 100644 --- a/Package.swift +++ b/Package.swift @@ -29,7 +29,10 @@ let package = Package( ), .testTarget( name: "TextCaptureKitTests", - dependencies: ["TextCaptureKit"] + dependencies: [ + "TextCaptureKit", + .product(name: "AppLogger", package: "applogger"), + ] ), ] ) diff --git a/README.md b/README.md index 5e70f9c..d27e48c 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ Camera capture, scanner UI, parsing, persistence, and product presentation remai API documentation is published with DocC after a GitHub release. See the [TextCaptureKit documentation](https://thatfactory.github.io/textcapturekit/documentation/textcapturekit/). +TextCaptureKit emits privacy-safe recognition outcomes through AppLogger with subsystem `com.thatfactory.textcapturekit`, category `recognition`, and the canonical 👁️ prefix. It never logs image data or recognized text. + ## Requirements - Swift 6.4 diff --git a/Sources/TextCaptureKit/TextCaptureLog.swift b/Sources/TextCaptureKit/TextCaptureLog.swift new file mode 100644 index 0000000..33f403d --- /dev/null +++ b/Sources/TextCaptureKit/TextCaptureLog.swift @@ -0,0 +1,52 @@ +import AppLogger + +/// Owns TextCaptureKit's unified-log identity and message rendering. +enum TextCaptureLogging { + static let category = "recognition" + static let emoji = "👁️" + static let subsystem = "com.thatfactory.textcapturekit" + + enum Event: Equatable { + case recognitionCancelled + case recognitionFailed + case recognitionSucceeded(hasObservations: Bool, level: TextCaptureRecognitionLevel) + + var level: AppLogLevel { + switch self { + case .recognitionCancelled, .recognitionSucceeded: + .debug + case .recognitionFailed: + .error + } + } + + var message: String { + switch self { + case .recognitionCancelled: + "\(emoji) recognize | result=cancelled" + case .recognitionFailed: + "\(emoji) recognize | result=failure, reason=vision" + case .recognitionSucceeded(let hasObservations, let level): + "\(emoji) recognize | result=success, observations=\(hasObservations ? "present" : "none"), level=\(level.logValue)" + } + } + + var isPrivate: Bool { false } + } + + static func emit(_ event: Event) { + AppLogger(subsystem: subsystem, category: category) + .log(level: event.level, event.message, isPrivate: event.isPrivate) + } +} + +extension TextCaptureRecognitionLevel { + fileprivate var logValue: String { + switch self { + case .accurate: + "accurate" + case .fast: + "fast" + } + } +} diff --git a/Sources/TextCaptureKit/TextCaptureRecognizer.swift b/Sources/TextCaptureKit/TextCaptureRecognizer.swift index 7056496..3d4c0aa 100644 --- a/Sources/TextCaptureKit/TextCaptureRecognizer.swift +++ b/Sources/TextCaptureKit/TextCaptureRecognizer.swift @@ -32,10 +32,18 @@ public struct TextCaptureRecognizer: Sendable { on: image.data, orientation: image.orientation.imagePropertyOrientation(for: image.data) ) + TextCaptureLogging.emit( + .recognitionSucceeded( + hasObservations: !observations.isEmpty, + level: configuration.recognitionLevel + ) + ) return TextCaptureResult(observations: observations.map(Self.mapObservation)) } catch is CancellationError { + TextCaptureLogging.emit(.recognitionCancelled) throw CancellationError() } catch { + TextCaptureLogging.emit(.recognitionFailed) throw TextCaptureError.recognitionFailed } } diff --git a/Tests/TextCaptureKitTests/TextCaptureLogTests.swift b/Tests/TextCaptureKitTests/TextCaptureLogTests.swift new file mode 100644 index 0000000..6f26ce3 --- /dev/null +++ b/Tests/TextCaptureKitTests/TextCaptureLogTests.swift @@ -0,0 +1,46 @@ +import AppLogger +import Testing + +@testable import TextCaptureKit + +struct TextCaptureLogTests { + @Test func eventsUseCanonicalEmojiAndStableMetadata() { + #expect(TextCaptureLogging.Event.recognitionCancelled.message == "👁️ recognize | result=cancelled") + #expect( + TextCaptureLogging.Event.recognitionFailed.message + == "👁️ recognize | result=failure, reason=vision" + ) + #expect( + TextCaptureLogging.Event.recognitionSucceeded(hasObservations: true, level: .accurate).message + == "👁️ recognize | result=success, observations=present, level=accurate" + ) + #expect( + TextCaptureLogging.Event.recognitionSucceeded(hasObservations: false, level: .fast).message + == "👁️ recognize | result=success, observations=none, level=fast" + ) + } + + @Test func loggingIdentityIsStable() { + #expect(TextCaptureLogging.category == "recognition") + #expect(TextCaptureLogging.emoji == "👁️") + #expect(TextCaptureLogging.subsystem == "com.thatfactory.textcapturekit") + } + + @Test func eventsUsePurposefulLevels() { + if case .debug = TextCaptureLogging.Event.recognitionCancelled.level { + } else { + Issue.record("Cancellation should be a debug event.") + } + if case .error = TextCaptureLogging.Event.recognitionFailed.level { + } else { + Issue.record("Failure should be an error event.") + } + if case .debug = TextCaptureLogging.Event.recognitionSucceeded(hasObservations: true, level: .accurate).level { + } else { + Issue.record("Success should be a debug event.") + } + + #expect(TextCaptureLogging.Event.recognitionCancelled.isPrivate == false) + #expect(TextCaptureLogging.Event.recognitionFailed.isPrivate == false) + } +} From f23ef5501b3a84b52384a76d2aa468bb22df4d27 Mon Sep 17 00:00:00 2001 From: Fernando Fernandes Date: Sun, 13 Sep 2026 20:51:03 +0200 Subject: [PATCH 3/3] Update AgentGuidelines to 0.0.32 --- AGENTS.md | 12 ++++++++++++ .../skills/agent-guidelines-audit/SKILL.md | 3 ++- AgentGuidelines/CHANGELOG.md | 7 +++++++ AgentGuidelines/Guidelines/Development.md | 2 ++ AgentGuidelines/Guidelines/Logging.md | 2 ++ AgentGuidelines/README.md | 10 +++++----- .../Scripts/validate_consumer_setup.swift | 3 +++ .../Scripts/validate_guidelines.swift | 3 +++ AgentGuidelines/Templates/AGENTS.md | 12 ++++++++++++ AgentGuidelines/Tests/run_tests.swift | 17 +++++++++++++++++ AgentGuidelines/VERSION | 2 +- 11 files changed, 66 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2c9fc7c..f1367f6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,6 +30,18 @@ Read only the guides relevant to the task: For an application that uses Redux, also read [Redux architecture](AgentGuidelines/Guidelines/Architecture/Redux.md). +Keep the following observability contract in the consumer repository's root `AGENTS.md` so implementation agents treat runtime diagnostics as part of lifecycle work. Copy it unchanged and update it when the marker version changes in this template. + +```md + +## Runtime Observability + +Treat privacy-safe runtime observability as part of implementing or changing stateful, asynchronous, fallible, or lifecycle-oriented behavior. Identify the meaningful success, failure, cancellation, recovery, and state-transition boundaries before handoff, and emit concise AppLogger events owned by the artifact that implements them. Dependency declaration or target linkage alone does not satisfy this requirement. + +Every ThatFactory package log starts with its canonical emoji and uses its own stable subsystem. Never log credentials, account or record identifiers, share URLs, captured content, images, or other user-generated values as public metadata. Keep pure values and utilities silent when they have no meaningful diagnostic event; record that deliberate decision in the implementation handoff instead of adding initializer or property-access noise. Follow [Logging](AgentGuidelines/Guidelines/Logging.md) for ownership, privacy, severity, message design, and tests. + +``` + Keep the following marked external-dependency contract in the consumer repository's root `AGENTS.md` so implementation agents receive the rule directly before they make dependency choices. Copy it unchanged and update it when the marker version changes in this template. ```md diff --git a/AgentGuidelines/.agents/skills/agent-guidelines-audit/SKILL.md b/AgentGuidelines/.agents/skills/agent-guidelines-audit/SKILL.md index e15df83..08a0b75 100644 --- a/AgentGuidelines/.agents/skills/agent-guidelines-audit/SKILL.md +++ b/AgentGuidelines/.agents/skills/agent-guidelines-audit/SKILL.md @@ -29,7 +29,8 @@ Review the actual change rather than only checking whether files exist: - Check that framework objects, persistence, logging, and asynchronous work remain in their allowed boundaries. - Check SwiftUI composition, narrow inputs, local versus durable state, localization, accessibility, and safe deterministic previews where applicable. - Check tests for the required framework, mirrored paths, shared tags, Given/When/Then structure, deterministic seams, and coverage of changed behavior and failure paths. -- Check logging ownership, subsystem, categories, emoji, privacy, severity, metadata stability, and noise controls when logging changed. +- Trace every new or changed stateful, asynchronous, fallible, or lifecycle-oriented behavior and verify that its owning artifact emits privacy-safe AppLogger events for the meaningful success, failure, cancellation, recovery, and state-transition outcomes needed to diagnose it. Dependency declaration and target linkage alone do not establish logging coverage. Accept silence for pure values or utilities only when there is no meaningful event boundary and the implementation handoff records that deliberate decision. +- Check logging ownership, subsystem, categories, emoji, privacy, severity, metadata stability, noise controls, and focused formatter or sink tests when logging changed. - For every Apple-platform application or Swift package in scope, except the AppLogger provider repository itself, verify integration with the shared [Logging guide](../../../Guidelines/Logging.md): confirm the AppLogger dependency is declared, the `AppLogger` library product is linked to every target that emits diagnostics, and any new project has it available in its primary runtime target before its first log call. Search the actual package or Xcode dependency graph rather than relying on an `import` alone, and treat `print`, direct `Logger` instances, or duplicate logging backends as incomplete integration when they emit project diagnostics. When implementation is authorized, add or repair the dependency and target linkage and migrate affected calls while preserving the guide's ownership, subsystem, category, emoji, privacy, severity, and noise rules; report an exact blocker when target or platform constraints make safe integration ambiguous. - Inspect dependency manifests, resolver or lock files, Xcode package references, vendored source or binary frameworks, and equivalent dependency declarations. Compare the change with the baseline and identify every new third-party dependency or expansion of an existing third-party dependency into a new target or runtime role. Apply the shared [external dependency policy](../../../Guidelines/Development.md#external-dependencies): require explicit repository-owner approval before the dependency is introduced and require the durable exception record in repository documentation. Do not infer approval merely from an execution plan, pull-request description, implementation convenience, package popularity, or the dependency already appearing in the diff. Treat an unapproved or undocumented third-party dependency as a blocker to completion. Do not flag Apple system frameworks, the Swift standard library, ThatFactory-owned packages, or guideline-mandated tooling used only for its documented tooling role. If a newly resolved transitive third-party package will be linked into or shipped with the product, verify that its owning direct dependency is covered by an approved exception rather than dismissing it solely because it is transitive. - Search dependency manifests, generated directories, project files, and documentation for CocoaPods or Carthage adoption. The shared [external dependency policy](../../../Guidelines/Development.md#external-dependencies) forbids both without an exception path and requires Swift Package Manager for package dependencies. When implementation is authorized, remove newly introduced adoption and its generated or configuration files; report pre-existing adoption as a completion blocker when safe migration is outside the task scope. diff --git a/AgentGuidelines/CHANGELOG.md b/AgentGuidelines/CHANGELOG.md index bd00f0c..019e186 100644 --- a/AgentGuidelines/CHANGELOG.md +++ b/AgentGuidelines/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project are documented in this file. +## [0.0.32] - 2026-09-13 + +### Changed + +- Made runtime observability an explicit consumer contract for changed stateful, asynchronous, fallible, and lifecycle behavior, while preserving silence for pure values and utilities without meaningful diagnostic boundaries. +- Extended the completion audit to require useful privacy-safe AppLogger outcome coverage instead of accepting dependency declaration and target linkage alone. + ## [0.0.31] - 2026-09-13 ### Added diff --git a/AgentGuidelines/Guidelines/Development.md b/AgentGuidelines/Guidelines/Development.md index c54a407..0c61a9f 100644 --- a/AgentGuidelines/Guidelines/Development.md +++ b/AgentGuidelines/Guidelines/Development.md @@ -52,3 +52,5 @@ Never discard unrelated changes to perform cleanup. If the original checkout is ## Logging Applications own their orchestration, lifecycle, and product-domain diagnostics. Follow the shared [logging guide](Logging.md) and rely on each dependency to log its own implementation. Do not duplicate or reformat package-internal operations in the application log. + +Treat observability as part of implementing or changing stateful, asynchronous, fallible, or lifecycle-oriented behavior. Before handoff, trace those boundaries and verify that privacy-safe AppLogger events distinguish the outcomes needed to diagnose the behavior in context. Merely adding the dependency or linking its product is not sufficient. Keep pure value and utility code silent when it has no meaningful event boundary, and record that deliberate decision in the handoff rather than manufacturing noisy logs. diff --git a/AgentGuidelines/Guidelines/Logging.md b/AgentGuidelines/Guidelines/Logging.md index 498c356..4cf3bb3 100644 --- a/AgentGuidelines/Guidelines/Logging.md +++ b/AgentGuidelines/Guidelines/Logging.md @@ -8,6 +8,7 @@ Use this guide for Apple-platform applications and Swift packages that emit runt - A consuming application logs its own orchestration and lifecycle events. It must not reproduce or reformat a dependency's internal steps or outcomes. - A reusable package describes events using its own domain language. Do not introduce concepts from one current client into package categories or messages. - Ownership does not require every API or package to emit logs. Pure utilities and operations without a meaningful diagnostic event may emit nothing. +- New or changed stateful, asynchronous, fallible, or lifecycle-oriented behavior must identify its meaningful diagnostic boundaries during implementation. Emit a privacy-safe outcome for success and each operationally distinct failure, cancellation, recovery, or state transition that a developer needs to distinguish. If an artifact is limited to pure values or utilities and has no such boundary, keep it silent and state that decision in the implementation handoff instead of adding initializer or property-access noise. - Logging is a side effect. It must not affect returned values, state transitions, error handling, or control flow. - Architectures that isolate side effects must call a logging package from an allowed side-effect boundary, such as middleware or a service, rather than from a pure reducer. @@ -56,6 +57,7 @@ Use this guide for Apple-platform applications and Swift packages that emit runt - Verify that every package message starts with its canonical emoji. - Verify the stable category, meaningful fields, privacy choice, log level, and single-emission behavior for each logged operation. - Do not make tests depend on querying the operating system's persisted log store. +- For every changed lifecycle covered by the observability requirement, test the event formatter or sink for its successful and operationally distinct unsuccessful outcomes. Dependency declaration and target linkage alone do not establish logging coverage. ## Filtering diff --git a/AgentGuidelines/README.md b/AgentGuidelines/README.md index 0c684f2..271b720 100644 --- a/AgentGuidelines/README.md +++ b/AgentGuidelines/README.md @@ -91,7 +91,7 @@ From the consumer repository root, install a tagged release: git subtree add \ --prefix=AgentGuidelines \ https://github.com/thatfactory/agent-guidelines.git \ - 0.0.31 \ + 0.0.32 \ --squash ``` @@ -109,7 +109,7 @@ Keep the subtree tracked, but add this to the consumer's tracked `.gitattributes AgentGuidelines/** linguist-generated ``` -Copy and adapt [the consumer template](Templates/AGENTS.md). Keep the consumer file small: describe the product or package, map its concrete physical folders, point to the applicable shared guides, and state only genuine exceptions. Keep the version-marked code-review contract, documentation-maintenance contract, and external-dependency contract directly in the repository-root `AGENTS.md`; Markdown links to shared guides are navigation, not automatic instruction includes. +Copy and adapt [the consumer template](Templates/AGENTS.md). Keep the consumer file small: describe the product or package, map its concrete physical folders, point to the applicable shared guides, and state only genuine exceptions. Keep the version-marked code-review contract, documentation-maintenance contract, external-dependency contract, and runtime-observability contract directly in the repository-root `AGENTS.md`; Markdown links to shared guides are navigation, not automatic instruction includes. Copy the shared [`.gitignore` template](Templates/.gitignore) into a new Xcode project or Swift package. Keep authored project files, workspaces, and package lockfiles eligible for version control, and follow the [Git ignore guidance](Guidelines/Git/IgnoreFiles.md) when an established consumer needs an additional project-specific rule. @@ -137,7 +137,7 @@ Validate the checked-in consumer integration directly or through the completion- AgentGuidelines/Scripts/validate_consumer_setup.swift ``` -The native Swift validator checks the version-marked root Code Review, Documentation Maintenance, and External Dependency contracts, Codex subtree-review scope, `.gitattributes`, local guide links, and the audit-skill symlink. When the root `AGENTS.md` links the shared Swift-format guide, it also requires both configuration symlinks and a non-mutating `lint-strict` CI invocation. Pass `--require-swift-format` only when auditing formatter adoption before adding that guide link. +The native Swift validator checks the version-marked root Code Review, Documentation Maintenance, External Dependency, and Runtime Observability contracts, Codex subtree-review scope, `.gitattributes`, local guide links, and the audit-skill symlink. When the root `AGENTS.md` links the shared Swift-format guide, it also requires both configuration symlinks and a non-mutating `lint-strict` CI invocation. Pass `--require-swift-format` only when auditing formatter adoption before adding that guide link. ## Update a consumer @@ -147,11 +147,11 @@ Review the target release's changelog, then pull it deliberately: git subtree pull \ --prefix=AgentGuidelines \ https://github.com/thatfactory/agent-guidelines.git \ - 0.0.31 \ + 0.0.32 \ --squash ``` -Confirm `AgentGuidelines/VERSION`, review the subtree diff, synchronize the marked code-review contract, documentation-maintenance contract, and external-dependency contract when their versions change, run `AgentGuidelines/Scripts/validate_consumer_setup.swift`, and run the consumer's relevant tests. Keep the subtree update in its own commit, and identify the old and new versions plus the central release or pull request in the consumer pull-request description. Updates are intentionally not automatic: one guideline release cannot silently change every project. +Confirm `AgentGuidelines/VERSION`, review the subtree diff, synchronize the marked code-review contract, documentation-maintenance contract, external-dependency contract, and runtime-observability contract when their versions change, run `AgentGuidelines/Scripts/validate_consumer_setup.swift`, and run the consumer's relevant tests. Keep the subtree update in its own commit, and identify the old and new versions plus the central release or pull request in the consumer pull-request description. Updates are intentionally not automatic: one guideline release cannot silently change every project. ## Maintain the source of truth diff --git a/AgentGuidelines/Scripts/validate_consumer_setup.swift b/AgentGuidelines/Scripts/validate_consumer_setup.swift index 43f41e3..e5b5c37 100755 --- a/AgentGuidelines/Scripts/validate_consumer_setup.swift +++ b/AgentGuidelines/Scripts/validate_consumer_setup.swift @@ -14,6 +14,8 @@ let documentationContractBegin = "" let externalDependencyContractBegin = "" let externalDependencyContractEnd = "" +let observabilityContractBegin = "" +let observabilityContractEnd = "" let markdownLinkPattern = #"\[[^\]]+\]\(([^)]+)\)"# let swiftFormatGuide = "AgentGuidelines/Guidelines/Swift/SwiftFormat.md" let strictFormatCommandPattern = @@ -335,6 +337,7 @@ func validateConsumerSetup( (contractBegin, contractEnd, "code-review"), (documentationContractBegin, documentationContractEnd, "documentation-maintenance"), (externalDependencyContractBegin, externalDependencyContractEnd, "external-dependency"), + (observabilityContractBegin, observabilityContractEnd, "runtime-observability"), ] for (begin, end, name) in contracts { let expected = extractMarkedBlock( diff --git a/AgentGuidelines/Scripts/validate_guidelines.swift b/AgentGuidelines/Scripts/validate_guidelines.swift index 655eadd..13b0890 100755 --- a/AgentGuidelines/Scripts/validate_guidelines.swift +++ b/AgentGuidelines/Scripts/validate_guidelines.swift @@ -270,6 +270,7 @@ func validateReadmeContract(_ errors: inout [String]) { "--require-swift-format": "explicit Swift-format adoption validation", "documentation-maintenance contract": "documentation contract synchronization", "external-dependency contract": "external dependency contract synchronization", + "runtime-observability contract": "runtime observability contract synchronization", ] for (value, description) in required where !contents.contains(value) { errors.append("README.md: missing \(description): '\(value)'") @@ -634,6 +635,7 @@ func validateAuditSkill(_ errors: inout [String]) { "lint-strict": "strict Swift-format CI audit", "AppLogger": "AppLogger integration audit", "Logging.md": "shared Logging guide reference", + "Dependency declaration and target linkage alone": "lifecycle observability coverage audit", "## Audit documentation consistency": "documentation drift audit", "Known stale documentation blocks completion": "stale documentation stopping rule", "## Audit documentation formatting": "documentation formatting audit", @@ -688,6 +690,7 @@ func validateAuditSkill(_ errors: inout [String]) { "AgentGuidelines/Guidelines/Development.md": "Development.md pointer", "BEGIN THATFACTORY DOCUMENTATION MAINTENANCE CONTRACT v1": "documentation-maintenance contract", "BEGIN THATFACTORY EXTERNAL DEPENDENCY CONTRACT v1": "external-dependency contract", + "BEGIN THATFACTORY RUNTIME OBSERVABILITY CONTRACT v1": "runtime-observability contract", "AgentGuidelines/Guidelines/Documentation.md": "Documentation.md pointer", "## Stack": "Stack section", ] diff --git a/AgentGuidelines/Templates/AGENTS.md b/AgentGuidelines/Templates/AGENTS.md index 96c6873..092c1a9 100644 --- a/AgentGuidelines/Templates/AGENTS.md +++ b/AgentGuidelines/Templates/AGENTS.md @@ -28,6 +28,18 @@ Read only the guides relevant to the task: For an application that uses Redux, also read [Redux architecture](AgentGuidelines/Guidelines/Architecture/Redux.md). +Keep the following observability contract in the consumer repository's root `AGENTS.md` so implementation agents treat runtime diagnostics as part of lifecycle work. Copy it unchanged and update it when the marker version changes in this template. + +```md + +## Runtime Observability + +Treat privacy-safe runtime observability as part of implementing or changing stateful, asynchronous, fallible, or lifecycle-oriented behavior. Identify the meaningful success, failure, cancellation, recovery, and state-transition boundaries before handoff, and emit concise AppLogger events owned by the artifact that implements them. Dependency declaration or target linkage alone does not satisfy this requirement. + +Every ThatFactory package log starts with its canonical emoji and uses its own stable subsystem. Never log credentials, account or record identifiers, share URLs, captured content, images, or other user-generated values as public metadata. Keep pure values and utilities silent when they have no meaningful diagnostic event; record that deliberate decision in the implementation handoff instead of adding initializer or property-access noise. Follow [Logging](AgentGuidelines/Guidelines/Logging.md) for ownership, privacy, severity, message design, and tests. + +``` + Keep the following marked external-dependency contract in the consumer repository's root `AGENTS.md` so implementation agents receive the rule directly before they make dependency choices. Copy it unchanged and update it when the marker version changes in this template. ```md diff --git a/AgentGuidelines/Tests/run_tests.swift b/AgentGuidelines/Tests/run_tests.swift index 3d61c70..6e87955 100755 --- a/AgentGuidelines/Tests/run_tests.swift +++ b/AgentGuidelines/Tests/run_tests.swift @@ -146,6 +146,23 @@ let tests: [(String, () throws -> Void)] = [ } } ), + ( + "repository validator rejects missing observability adoption guidance", + { + try withTemporaryDirectory { temporary in + let fixture = temporary.appendingPathComponent("repository") + try copyRepositoryFixture(to: fixture) + let readme = fixture.appendingPathComponent("README.md") + var contents = try String(contentsOf: readme, encoding: .utf8) + contents = contents.replacingOccurrences(of: "runtime-observability contract", with: "runtime contract") + try write(contents, to: readme) + let result = try run([fixture.appendingPathComponent("Scripts/validate_guidelines.swift").path]) + try require(!result.succeeded, "missing observability adoption guidance unexpectedly passed") + try require( + result.output.contains("missing runtime observability contract synchronization"), result.output) + } + } + ), ( "repository validator rejects private dependency authentication drift", { diff --git a/AgentGuidelines/VERSION b/AgentGuidelines/VERSION index d788d43..78bae5b 100644 --- a/AgentGuidelines/VERSION +++ b/AgentGuidelines/VERSION @@ -1 +1 @@ -0.0.31 +0.0.32