Skip to content

Decision Log

2026-07-18 — Start with an empty public repository

Decision: Create tmcoconsulting/evidenceops without importing history from an existing Intune or Apple GitOps repository.

Why: It avoids ambiguous licensing, proprietary history, and accidental tenant-data carryover.

2026-07-18 — Use Apache License 2.0

Decision: License original Provifact work under Apache License 2.0 and identify TMCO Consulting, LLC in NOTICE.

Why: The license is OSI-approved, contribution-friendly, and includes an express patent grant. The Apache Software Foundation license text is the authoritative license source. This is a project decision, not legal advice.

2026-07-18 — Keep the core dependency-free

Decision: The Phase 0 Python package has no runtime dependency. Direct development and docs tools are exact-pinned in pyproject.toml.

Why: The evidence engine and sanitizer use the standard library, keeping the trusted computing base understandable. MkDocs and the Material theme are development/documentation dependencies only. MkDocs Material uses the MIT license, so its generated static output does not change Provifact' Apache license.

Known risk: The pinned Material release prints an upstream warning about planned MkDocs 2.0 incompatibility and licensing concerns. Provifact remains on the pinned MkDocs 1.6.1 line in Phase 0 and will evaluate a documentation-stack change separately rather than silently upgrading.

2026-07-18 — Separate deterministic evidence from generated analysis

Decision: Drift state and evidence fingerprints are computed without a model. GPT-5.6 narrative is deferred and will be non-authoritative, labeled, cited, evaluated, and human-approved.

Why: Auditors must be able to reproduce evidence independently of a probabilistic narrative. The current Codex model guidance documents GPT-5.6 Sol and reasoning controls used for the implementation workflow; runtime API design remains a Phase 1 decision.

2026-07-18 — Make the provider contract read-only and vendor-neutral

Decision: The provider protocol exposes collection only and returns normalized observations.

Why: Evidence production does not require mutation authority. This preserves a narrow initial Intune boundary and avoids tying drift logic to one vendor's object model. Microsoft remains the authority for Graph permissions and Graph API practices.

2026-07-18 — Fail closed on unknown fields

Decision: Every public-artifact field requires an explicit allow, drop, or pseudonymize action.

Why: Provider schemas evolve. Automatic pass-through would make an upstream field addition a possible public disclosure.

2026-07-18 — Deploy Pages only after validation

Status: Superseded during Phase 1 security remediation; the workflow was removed.

Decision: A Pages workflow runs only after the CI workflow succeeds for main, rebuilds the site at the validated commit, scans it, and uses GitHub's supported Pages artifact/deploy actions.

Why: It keeps publication downstream of tests and the policy gate. GitHub documents custom Pages workflows and deployment protection rules.

2026-07-18 — Defer custom domain and live collection

Status: Superseded for hosting. Live collection remains deferred; the selected future hostname is now part of the Cloudflare Workers milestone below.

Decision: Use the initial GitHub Pages hostname. Do not configure DNS, Graph authentication, or live collection in Phase 0.

Why: Those actions require separately verified authorization, data inventory, permissions, and rollback planning. GitHub and Microsoft document the future Azure OIDC pattern and workload identity federation.

2026-07-18 — Use text-only project identity

Decision: Use a polished text identity and CSS, with no copied logo, prior thumbnail, or external brand asset.

Why: No corresponding asset with established usage rights was present in the working directory. TMCO Consulting, LLC is named as the legitimate project sponsor without using an unverified asset.

2026-07-18 — Bound Phase 1 to Graph v1.0 legacy configuration

Decision: Read only deviceConfigurations and their assignments; normalize two fields from macOSGeneralDeviceConfiguration. Do not use /beta Settings Catalog or managed-device inventory.

Why: Microsoft documents the configuration list, assignment list, and macOS resource in v1.0. A supported v1.0 Settings Catalog contract was not established. Device counts would add a permission without contributing to this narrow drift proof.

2026-07-18 — Request one Graph read permission

Decision: Use only delegated/application DeviceManagementConfiguration.Read.All, with its documented administrator consent requirement. Do not request its write counterpart, Directory.Read.All, or managed-device read scope.

Why: It is the least privileged permission Microsoft lists for both implemented endpoints. The IDs and rationale are machine-readable in manifests/microsoft-graph-permissions.v1.json and traceable to the Microsoft permissions reference.

2026-07-18 — Normalize instead of storing raw Graph exports

Decision: Retain source policy IDs only in a restrictive private package; never persist full Graph responses. Publication is a distinct, key-requiring, fail-closed command.

Why: Configuration read permission is broad even when code uses a narrow slice. Data minimization reduces disclosure, retention, and future schema-change risk.

2026-07-18 — Constrain GPT with structure and verification

Decision: Send only a validated public package (maximum 64 KiB) to the OpenAI Responses API, with no tools, store: false, strict json_schema, and the configured Build Week model gpt-5.6-terra. Treat the returned object as untrusted.

Why: OpenAI documents structured outputs and the Responses API. Structure narrows output shape; a separate deterministic verifier enforces evidence/status/claim rules and keeps human review authoritative.

2026-07-18 — Keep live automation out of the public repository

Decision: Document—but do not add—a private OIDC collection workflow. Public CI and the local static demo use no tenant or OpenAI credentials.

Why: GitHub OIDC and Entra workload federation can issue short-lived credentials, but their subjects, environments, tenant/application IDs, and retention are private deployment decisions. The public repository cannot safely supply universal tenant-specific values.

2026-07-18 — Retire GitHub Pages and select Cloudflare Workers Static Assets

Decision: Remove the GitHub Pages workflow_run deployment and its pages: write and deployment OIDC permissions. Keep MkDocs as a local/static build, and make Cloudflare Workers Static Assets at evidenceops.tmcoconsulting.com the next separately reviewed milestone. Route only same-origin /api/* through Worker code; serve other static assets directly.

Why: The Codex Security scan proved the branch-name-only workflow_run gate could select a fork-controlled SHA for execution in the privileged deployment job. Retiring the obsolete hosting path removes the sink rather than preserving unnecessary deployment privilege. Cloudflare documents selective run_worker_first routing for hybrid static/API applications. No Cloudflare resource, configuration, secret, DNS record, workflow, or deployment is created in this remediation.

2026-07-18 — Use one credential catalog at every egress

Decision: Publication sanitization, repository scanning, static-artifact scanning, and pre-model egress import one high-confidence credential-pattern catalog. The catalog includes every GitHub ghp_, gho_, ghu_, ghs_, ghr_, and github_pat_ form covered by regression tests.

Why: Duplicated patterns diverged and allowed underscore-form GitHub credentials to cross the publication boundary. One catalog makes a missed update visible to every gate and test.

2026-07-18 — Verify typed claims; quarantine unrestricted prose

Decision: Narrative explanations carry an additive typed deterministic_claim with closed finding_status code/value semantics. The verifier requires exact unique finding-ID coverage and marks only those typed claims verified. All free-form generated prose remains quarantined for human review, regardless of phrasing.

Why: A finite status-phrase matcher cannot establish the meaning of unrestricted natural language. Exact identifiers and enum values are deterministic; prose is not. Legacy schema-v1 narratives remain readable but cannot verify without typed claims.

2026-07-18 — Defer production OpenAI key and BYOK to the Worker milestone

Decision: The future production runtime should use a dedicated OpenAI project service-account key stored only as a Cloudflare Worker secret. The current OpenAI project retains its legacy evidenceops identifier during the coordinated brand migration. Fixture mode remains the default when credits or the key are unavailable. Browser BYOK is deferred pending a dedicated browser-key, logging, support, and abuse threat model.

Why: OpenAI recommends keeping API keys out of code and public repositories and supplying them through environment variables or a secret manager. Cloudflare provides encrypted Worker secret bindings. A browser-supplied key would create additional exposure surfaces and is not a safe "easy" addition without those controls. See OpenAI production practices and Cloudflare Worker secrets.

2026-07-18 — Implement a small same-origin Worker before provisioning

Decision: Implement and independently validate a strict TypeScript Worker and Static Assets configuration before creating Cloudflare resources, DNS, secrets, or deployment automation. Serve static paths directly and run only /api/* Worker-first. Default to fixture mode; make OpenAI mode explicit and never silently fall back.

Why: The runtime boundary can be reviewed, tested in workerd, bundled, and scanned without creating external state or consuming model credits. Same-origin checks, byte/time/rate bounds, allowlisted logs, shared publication scans, typed verification, and static security headers become evidence before production authorization is granted.

Dependencies: Worker development dependencies are exact-pinned in package.json and fully resolved in package-lock.json: Wrangler 4.112.0, TypeScript 7.0.2, Vitest 4.1.10, Cloudflare Vitest pool 0.18.6, Oxlint 1.74.0, Oxlint TypeScript companion 0.25.0, Prettier 3.9.5, and Node types 26.1.1. They are build/test tools, not Worker runtime packages. Their declared licenses are MIT, Apache-2.0, or MIT OR Apache-2.0, compatible with the repository's Apache-2.0 source license. Public CI uses Node 22 and npm ci against the lock.

Sources: Cloudflare documents Static Assets bindings, Vitest integration, and secret bindings. OpenAI documents the Responses API and structured outputs.

2026-07-18 — Deploy fixture-first and keep BYOK rejected

Decision: Deploy evidenceops with Workers Static Assets, a Worker Custom Domain, dual native rate limiters, and a fixed gpt-5.6-terra model policy. Store the dedicated OpenAI project key only as the Worker secret OPENAI_API_KEY. Keep production in explicit fixture mode after the single bounded live validation returned capacity unavailable. Do not accept browser-supplied keys.

Why: Terra is the documented balanced GPT-5.6 cost/capability tier. The public fixture preserves the end-to-end deterministic demonstration without a chargeable retry loop or misleading live-mode label. BYOK would make the application a browser credential processor and expand storage, exfiltration, support, logging, and abuse boundaries.

Sources: OpenAI documents GPT-5.6 Terra as the balanced cost/capability tier with Responses API and structured-output support. Cloudflare documents the Workers fetch request-context and public-routing rules.

Operational boundary: GitHub deployment is main-only, environment-protected, and disabled until a narrowly scoped Cloudflare API token is stored. The manual Intune workflow is likewise main-only and may authenticate only through the exact environment-scoped Entra federated identity. The trust and required application consent are configured, but no feature-branch or live Graph run was made.

2026-07-18 — Activate exact Entra environment federation without running collection

Decision: Add github-evidenceops-production with the exact GitHub environment subject and grant administrator consent only to application DeviceManagementConfiguration.Read.All. Create no client secret and do not execute the audit until reviewed code reaches main.

Why: The environment subject excludes pull requests and arbitrary branches from the workload identity. Keeping execution post-merge preserves the protected-code boundary while allowing the existing manual workflow to use a short-lived Graph token later.

Pre-existing state: Eight consented delegated permissions remain on the application. They are not used by the Provifact application-only workflow, were not added for this proof, and were not removed automatically because their ownership and unrelated consumers require human review.

2026-07-19 — Expand the Apple proof without adding mutation authority

Decision: Keep the provider-neutral and schema-v1 contracts, then add a separate Apple-focused collector and Mission schema. Use exactly four Graph read-only permission families: configuration, managed devices, managed applications, and service configuration. Retain one isolated beta dependency for Settings Catalog because an adequate v1.0 contract was not available at the implementation date.

Why: Policy evidence alone cannot explain Apple fleet, app, enrollment, assignment, and service health. Independent adapters make partial permission or schema failures visible without inventing a universal Intune response. The provider and transport still expose only collection/GET.

Rejected: Directory.Read.All, group/user scopes, raw tenant exports, write permissions, client secrets, and silent beta/v1 substitution.

Sources: Microsoft permissions reference, managed-device API, Apps and Books API, and APNs API.

2026-07-19 — Pin an internally approved mSCP demo baseline

Decision: Pin mSCP revision 11b5896e4f12f43410686024f543792742562c91 and its macOS 26 CIS Level 1 profile. Verify the source artifact and derived 98-rule inventory hashes. Approve it only as the “TMCO Consulting macOS CIS Level 1 Demo Baseline.” Map five settings using identifiers from the pinned source; keep all other rules visible but unsupported. Do not score iOS/iPadOS.

Why: Complete inventory visibility plus explicit mapping support avoids cherry-picking and false coverage. Framework IDs are deterministic source metadata, not model-created mappings.

License: Derived mSCP metadata is attributed under CC BY 4.0 in NOTICE; Apple vendor descriptions are excluded.

2026-07-19 — Use database-free sanitized history and a prefiltered assistant

Decision: Put current/previous sanitized snapshot deltas in the Mission package; do not add D1, KV, or R2. Add same-origin /api/ask, accepting only a bounded question and snapshot ID. Load the package server-side, classify a closed evidence intent, and send fewer than 16 KiB of allowlisted context to fixed gpt-5.6-terra with store: false, no tools, low reasoning, strict JSON, exact typed claims, and prose quarantine. Continue to reject BYOK.

Why: This proves history and grounded explanation without adding a persistence product or a browser credential boundary. Prefiltering is cheaper and safer than sending a whole package.

2026-07-19 — Add non-executing CodeQL analysis

Decision: Add an exact-commit-pinned CodeQL v4 workflow for Python and JavaScript/TypeScript. Both interpreted-language analyses use build-mode: none, so the scanner analyzes pull-request source without executing it. Keep CodeQL separate from the required CI gate and grant only repository/action read access plus security-events: write for analysis upload.

Why: GitHub's code-scanning API reported that no analysis existed. CodeQL is supported for this public repository and adds data-flow analysis without giving untrusted pull-request code a secret, deployment identity, or build step.

Platform limitation: GitHub's non-provider secret-pattern and secret-validity status remained disabled after a repository-API enable request, so Provifact does not claim those controls are active. Provider secret scanning, push protection, the shared credential catalog, and fail-closed repository/public-artifact scans remain enabled requirements.

2026-07-19 — Keep Graph type fallbacks inside a closed public taxonomy

Decision: Normalize a missing Microsoft Graph @odata.type to unknown, not the dotted SDK namespace fallback microsoft.graph.unknown. Keep the public domain detector unchanged.

Why: The first protected-main live audit completed GET-only collection, but the publication boundary correctly rejected the dotted fallback as a domain-shaped value. Dotted provider namespaces are unnecessary in the public resource summary. Reducing the fallback to the existing closed taxonomy retains useful resource-family evidence without allowlisting an arbitrary domain pattern or exposing a tenant value.

2026-07-19 — Separate live collection from public deployment

Decision: Keep live collection manual and main-only. Add an opt-in, disabled-by-default handoff that retains exactly one already scanned public Mission file for one day. Require a separate protected deployment run, selected by the exact audit run ID, to redownload, strictly revalidate, rebuild, scan, and publish it. Never upload the private package or containing directory.

Why: A successful sanitizer is necessary but not sufficient authorization to publish tenant-derived evidence. The two-workflow boundary preserves a human inspection point, prevents private artifact retention, and gives the deployed revision an auditable source without adding a database or a scheduled live workflow. Nested public metadata now uses explicit field allowlists so a recomputed fingerprint cannot legitimize an unknown field.

2026-07-19 — Keep routine Worker uploads outside custom-domain management

Decision: Treat evidenceops.tmcoconsulting.com as a separately provisioned Cloudflare control-plane resource. Omit routes/custom domains from the routine production Wrangler upload, require an explicit manual confirmation input, keep the emergency environment enable flag false outside reviewed windows, and verify the exact deployed snapshot ID after upload.

Why: The account token has only Workers Scripts write access. It successfully uploaded and activated the reviewed Worker/assets but could not inspect a zone route that routine code deployment does not need to manage. Separating these concerns preserves the narrow token, avoids DNS/route mutation authority, and makes a wrong or stale deployment fail the exact runtime status check.

2026-07-19 — Preserve Bot Fight Mode during deployment verification

Decision: Do not bypass or disable Cloudflare Bot Fight Mode for GitHub-hosted runners. Bind the expected Mission snapshot ID to the uploaded Worker version through Wrangler's version message, then re-read versions and deployments and require that exact version to be the sole version serving 100% of traffic. Perform public HTTPS, status, header, and browser checks independently after the protected workflow.

Why: The first route-isolated retry uploaded and activated the reviewed bundle, but its immediate curl was managed-challenged at the edge. Cloudflare Security Analytics correlated the exact timestamp and classified the event as Bot Fight Mode. Cloudflare documents that a Worker version captures code, static assets, bindings, and compatibility settings, while a deployment identifies the version receiving traffic. Control-plane verification proves activation without weakening an unrelated edge defense or granting the deployment token zone-security authority.

Source: Cloudflare Versions and deployments and Wrangler Worker commands.

2026-07-20 — Make live evidence mandatory for production and bound one prior snapshot

Decision: Remove synthetic generation and optional artifact selection from the production workflow. Require the exact reviewed trusted-main audit run ID and Mission snapshot ID, validate both before upload, and bind the same snapshot to the sole active Worker version. Permit the protected Intune audit to download only one named sanitized public artifact from a successful prior trusted-main audit and pass it through --previous-public; never download a prior private package. Serve both /api/* and the public Mission JSON with Cache-Control: no-store.

Why: Production cannot fail closed if a missing selector silently rebuilds fixtures. Exact run/snapshot binding preserves the human publication boundary and makes a stale or mismatched artifact non-deployable. A single prior public package is sufficient to demonstrate resolved and new drift without adding a database, retaining raw tenant evidence, or widening the Entra trust.

2026-07-20 — Use owner-approved TMCO Consulting brand assets locally

Decision: Check the public TMCO Consulting favicon and social image into Provifact rather than hotlinking them. Record source URLs and SHA-256 hashes beside the assets, use the full company name in user-facing copy, and retain TMCO Consulting, LLC for legal and approval contexts. Run a content check in CI that rejects standalone abbreviated company-name copy.

Why: Local assets avoid a runtime dependency on the company site and preserve an auditable brand source. TJ owns and explicitly authorized the marks for Provifact. The repository notice clarifies that Apache-2.0 does not grant trademark rights.

2026-07-20 — Require exact reviewed provider IDs and keep unknown mappings honest

Decision: Replace semantic-key substring matching with a versioned Microsoft Intune provider mapping registry. Allow only exact case-normalized aliases supported by a reviewed public Microsoft reference. Four macOS settings are reviewed; firewall stealth mode remains explicitly unreviewed until its Graph definition ID is independently established. A mapping-not-reviewed, unsupported value, or collection-gap state cannot be converted into Missing from tenant.

Why: Synthetic semantic keys can conceal a failed join to real Settings Catalog identifiers. Exact aliases and closed value transforms make false absence claims testable and keep the live sanitized collection—not tenant display names or AI inference—the authority.

Source: Microsoft’s public intune-my-macs documentation for the reviewed macOS provider definition IDs. The pinned mSCP revision remains the desired-state and framework cross-reference source.

2026-07-20 — Run live Assistant only in production and never fall back to fixtures

Decision: Keep local and preview deployments in deterministic fixture mode. Configure production for exactly gpt-5.6-terra and require its encrypted Worker secret. Accept only a question, closed page enum, current snapshot ID, and optional selected evidence reference; the Worker selects the bounded sanitized context. Preserve store: false, no tools, rate/size/time/output limits, exact typed claims, reference verification, prose quarantine, and BYOK rejection.

Why: A site-wide assistant is useful only when it can explain the current published evidence without becoming an evidence source or credential processor. A failed production model request must remain a visible failure rather than silently returning a fixture that appears live.

2026-07-20 — Flatten documented Settings Catalog groups and keep platform scope explicit

Decision: Traverse only Microsoft Graph's documented bounded group, choice, and simple setting instance containers. Emit scalar child instances as separate evidence records and retain exact settingDefinitionId values. Accept the public Microsoft FileVault Enable choice token only as a closed reviewed alias value. Filter policies that explicitly declare a known non-Apple platform without creating an Apple collection gap; keep unknown platform and value shapes fail closed.

Why: The production package showed Settings Catalog as globally incomplete even though the FileVault policy existed. Two distinct issues caused the false gap: the FileVault setting lived under a group collection parent that the adapter treated as the setting, and a known out-of-scope policy was recorded as an Apple collection failure. Broader Graph permission cannot repair a parser that does not follow the documented response shape.

Sources: Microsoft Graph deviceManagementConfigurationSetting, deviceManagementConfigurationGroupSettingCollectionInstance, and Microsoft's public intune-my-macs FileVault policy.

2026-07-20 — Make Mission Control operational without pretending STIG is loaded

Decision: Keep the reviewable MkDocs/Static Assets stack but render the operational page as a full-width NOC-style console. Show desired versus observed state, exact evidence counts, collection flow, deterministic findings, blind spots, and an interactive STIG technical-reference lens. Label STIG NOT LOADED and explain the approval and mapping work required for a real baseline switch.

Why: Documentation navigation is useful for operators, but it should not dominate the evidence decision surface. A visual framework change does not alter the authority boundary: the current CIS desired state and deterministic evidence remain authoritative, and cross-references never become a STIG score or compliance verdict.

2026-07-20 — Separate public Mission and model-response byte ceilings

Decision: Keep OpenAI response reads capped at 256 KiB. Apply one shared 512 KiB public-Mission ceiling at Python loading, production promotion, and Worker readiness before schema, fingerprint, and egress validation. Reject a public Mission above that dedicated cap before it can be accepted as publishable.

Why: The first live package after nested FileVault normalization was 268,320 bytes because it retains the complete 98-rule review inventory. Reusing the smaller untrusted-model response cap made /api/status return 502 even though the static package was valid. Increasing the model-response limit would have unnecessarily weakened the OpenAI boundary; a distinct public-Mission cap keeps the handoff and runtime consistent while preserving the smaller model boundary.

2026-07-20 — Rebrand the product as Provifact™ by TMCO Consulting

Decision: Adopt Provifact™ by TMCO Consulting as the public product name and From approved change to audit-ready proof. as the product tagline. Update public UI, documentation, runtime labels, package metadata, and the preferred CLI. Preserve the existing Python import namespace, schema and algorithm identifiers, environment-variable prefix, GitHub repository slug, Cloudflare Worker name, OpenAI project identifier, and transition hostname as explicitly documented legacy technical identifiers until a coordinated external cutover.

Why: The prior product name conflicts with an existing name. A blind global rename would break the Entra federated credential subject bound to the GitHub repository, detach deployment history and encrypted Worker secrets, invalidate stable fingerprints/schema contracts, and complicate rollback. Brand presentation can change immediately while infrastructure identifiers migrate through a separately reviewed cutover.

2026-07-20 — Treat the complete Level 1 inventory as an implementation plan

Decision: Show all 98 internally approved Level 1 rules by default, using human-readable titles from the already-pinned NIST mSCP revision. Keep the four exact reviewed Intune joins as the only deterministic alignment denominator. Classify the other 94 rules as either exact provider-mapping review or management/evidence-path planning, grouped by baseline section. Do not generate drift findings, failed-control language, or framework verdicts for unevaluated rules.

Why: The prior interface technically carried all 98 rules but hid 94 behind a checked filter and rendered only evaluated rows in Mission Control. That made a complete approved inventory look like a four-rule baseline. A visible implementation backlog helps teams onboarding Macs plan Settings Catalog, custom-profile, script/agent, and alternate-evidence work without weakening the exact-ID join or pretending uncollected evidence proves noncompliance.

Source: The title catalog was checked against the repository's pinned revision of the NIST macOS Security Compliance Project. Dotted product-like tokens in two titles are expanded to ordinary words so the public fail-closed domain scanner remains unchanged.

2026-07-20 — Use an original Provifact mark and Provifact Assistant name

Decision: Replace the temporary letter tile with an original project-local Provifact vector mark. Name the bounded evidence explainer Provifact Assistant, not “Copilot,” throughout the public interface, documentation, browser state, and code-facing UI hooks.

Why: Provifact needs a recognizable identity at navigation, favicon, dashboard, and small-icon sizes. Microsoft permits truthful wordmark references to its products, but its current trademark guidance says third-party app names and brand assets must remain unique and must not use Microsoft's brand assets in a product or service name without permission. Provifact Assistant is also more specific: it explains sanitized deterministic evidence and is not a general-purpose coding or productivity copilot.

Source: Microsoft Trademark and Brand Guidelines.

2026-07-20 — Keep deterministic Assistant metadata server-owned

Decision: Send only bounded sanitized facts and the operator question to the fixed OpenAI model. Ask the model for strict explanatory fields, then attach typed deterministic claims and evidence references from the Worker-selected context. Add the mandatory human-review boundary in trusted code, verify the complete response server-side, and repeat strict shape and canonical claim checks in the browser.

Why: Requiring a generative model to reproduce authoritative claim objects and evidence IDs made otherwise safe prose fail verification and gave the model unnecessary influence over deterministic metadata. The model now cannot add, omit, or alter the authoritative claims or references.

Source: OpenAI's official Structured Outputs documentation for strict Responses API text.format schemas. Application-side evidence and policy verification remains mandatory.

2026-07-20 — Compare pinned public profiles by exact membership

Decision: Build a public comparison catalog from one local archive of the already-pinned NIST mSCP revision. Copy only profile membership, rule IDs, titles, sections, attribution, and source fingerprints. Load 16 public technical profiles and compare them to TMCO Consulting Approved by exact rule-ID set overlap. Reject an archive whose root does not match the pinned revision.

Why: Operators need to see where their approved baseline overlaps or differs from reference profiles without converting a crosswalk into a compliance score. Keeping profile membership, company approval, deterministic observation, and assessor judgment as separate states makes the adoption backlog visible and reviewable.

Source: NIST macOS Security Compliance Project, pinned to revision 11b5896e4f12f43410686024f543792742562c91 and attributed under CC BY 4.0.

2026-07-20 — Coordinate the Provifact repository and hostname cutover

Decision: Rename the GitHub repository to tmcoconsulting/provifact and make provifact.tmcoconsulting.com the public hostname. Preserve the internal Cloudflare Worker, OpenAI project/key labels, Python namespace, environment prefix, artifact prefix, and schema IDs where changing them would add migration risk without user value. Use repository-relative workflow provenance. Create and verify a post-rename immutable GitHub OIDC subject in Entra before retiring the prior subject.

Why: Public naming should be coherent, but encrypted secrets, version history, artifact provenance, and federation trust must not be broken or silently broadened during a cosmetic rename. The prior hostname remains a rollback surface until the new custom domain, TLS, protected audit, and deployment are independently verified.

Verified outcome: The repository was renamed without changing its immutable repository ID. The new environment-scoped Entra credential completed protected-main audit run 29780265224; only then was the old repository-name credential deleted. Cloudflare attached the new custom domain without replacing the old rollback hostname, and protected deployment 29780852414 published only the exact scanned snapshot selected from that audit. The deployment window was restored to disabled.

2026-07-20 — Canonicalize the one pinned upstream rule-ID variance

Decision: Map the pinned upstream identifier os_safari_prevent_cross-site_tracking_enable only through the explicit catalog alias implemented in 2ca6073, and require exact set equality between all Mission requirement rule IDs and the approved catalog profile before rendering a comparison.

Why: The upstream profile's actual identifier uses a hyphen in cross-site, while Provifact's reviewed normalized baseline uses an underscore. Count-only validation let the comparison table render a false 99th reference-only row even though the overlap summary was correct. A narrow alias plus fail-closed set validation fixes the known source variance without broadly rewriting future identifiers or merging unrelated rules.

The browser initially rejected an already-open page after deployment because its prior force-cache response contained the old catalog. The catalog URL is now version-bound to the expected fingerprint, and validation requires both that expected fingerprint and a recomputed canonical hash. New code therefore selects its matching immutable cache key while altered or stale catalog content continues to fail closed.

2026-07-20 — Refresh pinned CI runtime and artifact dependencies together

Decision: Consolidate the independently green Dependabot proposals for filelock 3.31.1, actions/checkout 7.0.1, actions/setup-python 7.0.0, and actions/setup-node 7.0.0 with the actions/upload-artifact 7.0.1 and actions/download-artifact 8.0.1 upgrades. Keep every action reference pinned to the exact Dependabot-supplied commit SHA and update the workflow-security tests to require those new immutable pins.

Why: The action-major updates move the workflows off the deprecated Node 20 action runtime. The two artifact PRs failed only because the security tests correctly required the previous SHAs; changing the workflow and its exact-pin assertions in one trusted-source change preserves that fail-closed control. No workflow permission, trigger, environment boundary, artifact path, or retention rule changes.