diff --git a/AGENTS.md b/AGENTS.md index c5341ef..9b4dd56 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 @@ -111,3 +123,4 @@ Replace these examples with exact repository paths: - Do not encode CEFR, product progression, CloudKit, or application-specific deduplication policy. - Store only opaque host-owned media references, never framework image objects or temporary URLs. +- LexiconKit currently emits no runtime diagnostics because its public surface contains only pure domain values and initializers. Do not log value construction or vocabulary content. If a stateful or fallible lifecycle is added, route its diagnostics through a package-local `LexiconLogging` gateway with subsystem `com.thatfactory.lexiconkit` and canonical emoji 📖. 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 b561cb9..019e186 100644 --- a/AgentGuidelines/CHANGELOG.md +++ b/AgentGuidelines/CHANGELOG.md @@ -2,6 +2,25 @@ 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 + +- 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 + +- Prohibited dedicated License headings and conventional standalone license-description paragraphs in Swift package READMEs while retaining root license files and standard license badges. + ## [0.0.29] - 2026-09-12 ### Added 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/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/Guidelines/Packages.md b/AgentGuidelines/Guidelines/Packages.md index c5a27d0..55b1b17 100644 --- a/AgentGuidelines/Guidelines/Packages.md +++ b/AgentGuidelines/Guidelines/Packages.md @@ -53,7 +53,8 @@ The common package baseline is Swift, Xcode, Platforms, License, and CI. Add opt - Use descriptive `alt` text. Preserve a repository's established Xcode badge convention when the label intentionally records the last verified Xcode version. - Prefer dynamic Updated and Revision badges backed by repository history or releases so maintainers do not edit dates and versions by hand. - Do not advertise a platform, integration, package manager, or agent that the repository does not support. -- Keep the repository's license in a root `LICENSE` file when reuse or redistribution is permitted. A README license heading is optional; the badge is a summary, not the license grant. +- Keep the repository's license in a root `LICENSE` file when reuse or redistribution is permitted. +- Retain the README license badge when the standard badge set applies, but do not add a dedicated License heading or license-description section to a package README. GitHub already presents the repository license beside the README, and the badge provides the summary without duplicating license prose. - Do not add a license to an existing repository without the owner's explicit choice of terms. ## Package boundaries diff --git a/AgentGuidelines/README.md b/AgentGuidelines/README.md index da03cc0..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.29 \ + 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.29 \ + 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 cf6a9a5..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 = @@ -21,6 +23,10 @@ let strictFormatCommandPattern = let mutatingFormatCommandPattern = #"(?m)^[ \t]*(?:-\s+)?(?:run:\s*)?(?:\./)?AgentGuidelines/Scripts/swift_format\.sh\s+format(?:-and-lint)?(?=\s|\\|$)"# let generatedAttributePattern = #"(?m)^\s*AgentGuidelines/\*\*\s+linguist-generated\s*$"# +let readmeLicenseHeadingPattern = #"(?im)^[ \t]{0,3}#{1,6}[ \t]+license[ \t]*#*[ \t]*$"# +let readmeSetextUnderlinePattern = #"^[ \t]{0,3}(?:=+|-+)[ \t]*$"# +let readmeLicenseParagraphPattern = + #"(?i)^.+?\s+is\s+available\s+under\s+the\s+.+?\s+license\.\s+See\s+\[LICENSE\]\((?:\./)?LICENSE\)\.$"# /// Parsed consumer-validation command-line values. struct Arguments { @@ -167,6 +173,75 @@ func files(in directory: URL, extensions: Set) -> [URL] { return values.filter { extensions.contains($0.pathExtension.lowercased()) }.sorted { $0.path < $1.path } } +/// Returns a Markdown fence marker and run length when a line can open or close a fenced code block. +func markdownFence(in line: String) -> (character: Character, length: Int, suffix: Substring)? { + let indentation = line.prefix(while: { $0 == " " }).count + let content = line.dropFirst(indentation) + guard indentation <= 3, let character = content.first, character == "`" || character == "~" + else { + return nil + } + let length = content.prefix(while: { $0 == character }).count + guard length >= 3 else { return nil } + return (character, length, content.dropFirst(length)) +} + +/// Returns whether a line is eligible to be the title of a Setext License heading. +func isSetextLicenseTitle(_ line: String) -> Bool { + let indentation = line.prefix(while: { $0 == " " }).count + guard indentation <= 3 else { return false } + return line.dropFirst(indentation).trimmingCharacters(in: .whitespaces) + .localizedCaseInsensitiveCompare("License") == .orderedSame +} + +/// Validates package README license content without inspecting vendored or non-package documentation. +func validatePackageReadme(consumerRoot: URL, errors: inout [String]) { + guard FileManager.default.fileExists(atPath: consumerRoot.appendingPathComponent("Package.swift").path) else { + return + } + let readmeURL = consumerRoot.appendingPathComponent("README.md") + guard FileManager.default.fileExists(atPath: readmeURL.path), + let contents = readText(readmeURL, errors: &errors, label: "consumer package README") + else { + return + } + var outsideFences: [String] = [] + var activeFence: (character: Character, length: Int)? + for line in contents.components(separatedBy: .newlines) { + if let currentFence = activeFence { + if let marker = markdownFence(in: line), marker.character == currentFence.character, + marker.length >= currentFence.length, + marker.suffix.trimmingCharacters(in: .whitespaces).isEmpty + { + activeFence = nil + } + outsideFences.append("") + } else if let marker = markdownFence(in: line), + marker.character == "~" || !marker.suffix.contains("`") + { + activeFence = (marker.character, marker.length) + outsideFences.append("") + } else { + outsideFences.append(line) + } + } + let governedContents = outsideFences.joined(separator: "\n") + let lines = governedContents.components(separatedBy: .newlines) + let hasSetextLicenseHeading = lines.indices.dropLast().contains { index in + isSetextLicenseTitle(lines[index]) && !matches(readmeSetextUnderlinePattern, in: lines[index + 1]).isEmpty + } + if !matches(readmeLicenseHeadingPattern, in: governedContents).isEmpty || hasSetextLicenseHeading { + errors.append("consumer package README: README.md must not contain a dedicated License heading") + } + let paragraphs = governedContents.components(separatedBy: "\n\n").map { + $0.split(whereSeparator: \.isNewline).joined(separator: " ") + .trimmingCharacters(in: .whitespacesAndNewlines) + } + if paragraphs.contains(where: { !matches(readmeLicenseParagraphPattern, in: $0).isEmpty }) { + errors.append("consumer package README: README.md must not contain a standalone license-description paragraph") + } +} + /// Validates non-mutating Swift-format CI integration. func validateSwiftFormatCI(consumerRoot: URL, errors: inout [String]) { let workflowsRoot = consumerRoot.appendingPathComponent(".github/workflows") @@ -262,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( @@ -304,6 +380,7 @@ func validateConsumerSetup( if swiftFormatAdopted { validateSwiftFormatCI(consumerRoot: consumerRoot, errors: &errors) } + validatePackageReadme(consumerRoot: consumerRoot, errors: &errors) } /// Parses command-line arguments. diff --git a/AgentGuidelines/Scripts/validate_guidelines.swift b/AgentGuidelines/Scripts/validate_guidelines.swift index bbbea8d..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)'") @@ -579,11 +580,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)'") @@ -594,6 +605,10 @@ func validateExternalDependencyPolicy(_ errors: inout [String]) { "[external dependency policy](Development.md#external-dependencies)": "package dependency policy pointer", "must not introduce or conceal a third-party runtime dependency": "first-party package boundary", "guideline-mandated tooling dependency": "DocC tooling exception", + "root `LICENSE` file": "root license-file retention", + "Retain the README license badge": "README license-badge retention", + "do not add a dedicated License heading or license-description section": + "README license-section prohibition", ] for (value, description) in required where !contents.contains(value) { errors.append("Guidelines/Packages.md: missing \(description): '\(value)'") @@ -620,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", @@ -674,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 21d3eaa..6e87955 100755 --- a/AgentGuidelines/Tests/run_tests.swift +++ b/AgentGuidelines/Tests/run_tests.swift @@ -74,6 +74,47 @@ func initializeRepository(at root: URL) throws { try git("config", "user.name", "Agent", in: root) } +func createConsumerFixture(at root: URL, isPackage: Bool, readme: String? = nil) throws { + try fileManager.createSymbolicLink( + at: root.appendingPathComponent("AgentGuidelines"), withDestinationURL: repositoryRoot) + try fileManager.copyItem( + at: repositoryRoot.appendingPathComponent("Templates/AGENTS.md"), + to: root.appendingPathComponent("AGENTS.md")) + try write("AgentGuidelines/** linguist-generated\n", to: root.appendingPathComponent(".gitattributes")) + let skillParent = root.appendingPathComponent(".agents/skills") + try fileManager.createDirectory(at: skillParent, withIntermediateDirectories: true) + try fileManager.createSymbolicLink( + at: skillParent.appendingPathComponent("agent-guidelines-audit"), + withDestinationURL: repositoryRoot.appendingPathComponent(".agents/skills/agent-guidelines-audit") + ) + try fileManager.createSymbolicLink( + at: root.appendingPathComponent(".swift-format"), + withDestinationURL: repositoryRoot.appendingPathComponent("Configurations/Swift/.swift-format")) + try fileManager.createSymbolicLink( + at: root.appendingPathComponent(".editorconfig"), + withDestinationURL: repositoryRoot.appendingPathComponent("Configurations/Swift/.editorconfig")) + try write( + """ + name: CI + on: + pull_request: + push: + branches: [main] + jobs: + swift-format: + steps: + - run: AgentGuidelines/Scripts/swift_format.sh lint-strict Package.swift Sources Tests + """ + "\n", + to: root.appendingPathComponent(".github/workflows/ci.yml") + ) + if isPackage { + try write("// swift-tools-version: 6.0\n", to: root.appendingPathComponent("Package.swift")) + } + if let readme { + try write(readme, to: root.appendingPathComponent("README.md")) + } +} + func localization(_ value: String, state: String = "translated") -> String { #"{"stringUnit":{"state":"\#(state)","value":"\#(value)"}}"# } @@ -105,6 +146,44 @@ 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", + { + 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", { @@ -469,6 +548,101 @@ let tests: [(String, () throws -> Void)] = [ } } ), + ( + "consumer validator rejects package README License headings", + { + for readme in [ + "# Package\n\n## License\n", "# Package\n\n#### lIcEnSe ####\n", + "# Package\n\nLicense\n-------\n", "# Package\n\nLICENSE\n=======\n", + ] { + try withTemporaryDirectory { root in + try createConsumerFixture(at: root, isPackage: true, readme: readme) + let result = try run([ + script("Scripts/validate_consumer_setup.swift"), "--consumer-root", root.path, + ]) + try require(!result.succeeded, "package License heading unexpectedly passed") + try require(result.output.contains("must not contain a dedicated License heading"), result.output) + } + } + } + ), + ( + "consumer validator rejects standalone package README license prose", + { + try withTemporaryDirectory { root in + try createConsumerFixture( + at: root, + isPackage: true, + readme: "# Example\n\nExample is available under the MIT license. See [LICENSE](LICENSE).\n" + ) + let result = try run([ + script("Scripts/validate_consumer_setup.swift"), "--consumer-root", root.path, + ]) + try require(!result.succeeded, "standalone package license prose unexpectedly passed") + try require( + result.output.contains("must not contain a standalone license-description paragraph"), + result.output + ) + } + } + ), + ( + "consumer validator accepts package license badge and absent README", + { + try withTemporaryDirectory { root in + try createConsumerFixture( + at: root, + isPackage: true, + readme: """ + # Example + + ![License](https://img.shields.io/badge/License-MIT-green.svg) + + See [LICENSE](LICENSE) when verifying redistribution terms. + + ````markdown + ```markdown + ## License section example + + Example is available under the MIT license. See [LICENSE](LICENSE). + ``` + ```` + + ~~~text + # License + ~~~ + """ + "\n" + ) + let result = try run([ + script("Scripts/validate_consumer_setup.swift"), "--consumer-root", root.path, + ]) + try require(result.succeeded, result.output) + } + try withTemporaryDirectory { root in + try createConsumerFixture(at: root, isPackage: true) + let result = try run([ + script("Scripts/validate_consumer_setup.swift"), "--consumer-root", root.path, + ]) + try require(result.succeeded, result.output) + } + } + ), + ( + "consumer validator limits README license policy to package roots", + { + try withTemporaryDirectory { root in + try createConsumerFixture(at: root, isPackage: false, readme: "# App\n\n## License\n") + try write( + "# License\n\nStandalone documentation may discuss licensing.\n", + to: root.appendingPathComponent("Documentation/README.md") + ) + let result = try run([ + script("Scripts/validate_consumer_setup.swift"), "--consumer-root", root.path, + ]) + try require(result.succeeded, result.output) + } + } + ), ( "consumer validator accepts synchronized integration", { diff --git a/AgentGuidelines/VERSION b/AgentGuidelines/VERSION index 369bd4c..78bae5b 100644 --- a/AgentGuidelines/VERSION +++ b/AgentGuidelines/VERSION @@ -1 +1 @@ -0.0.29 +0.0.32 diff --git a/CHANGELOG.md b/CHANGELOG.md index 291b7d7..ed751a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to LexiconKit are documented here. ## Unreleased +### Changed + +- Documented the package's deliberate no-event logging decision for its current pure-value API and reserved its future logging identity. + ### Added - Added persistence-friendly vocabulary entries, terms, definitions, provenance, tags, opaque asset references, and explicit timestamps. diff --git a/README.md b/README.md index 5156fe0..4888e27 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,10 @@ let entry = LexiconEntry( API documentation is published with DocC after a GitHub release. See the [LexiconKit documentation](https://thatfactory.github.io/lexiconkit/documentation/lexiconkit/). +## Runtime diagnostics + +LexiconKit currently emits no runtime diagnostics. Its public surface contains only pure domain values and initializers; persistence, synchronization, validation workflows, and application lifecycle remain host responsibilities. Logging value construction would add noise and could expose vocabulary content. If the package later gains stateful or fallible runtime behavior, its diagnostics will use a package-local `LexiconLogging` gateway, subsystem `com.thatfactory.lexiconkit`, and the canonical 📖 prefix. + ## Requirements - Swift 6.4