Scope: report only, zero fixes. Audits the three quality gates so CI can be
made trustworthy. Produced after PR #88 merged (c30506d): the resolver-token auth
and the ^2.0 Ouroboros pin are in main; the PHPCS src/ debt is unchanged and
intentionally left for this audit to characterise.
Method: static analysis of the committed gate configuration and the local source
/ test tree. No code was modified. Tests are evaluated against spec
(PAM-002, PAM-003, Ouroboros v2.0.0 shared-test-vectors.json) — never against the
current code.
Authority: naming/convention recommendations in §A.2 are governed by
SPARXSTAR-Engineering-Standards-v1.0 §6.2 — PascalCase classes, Sparxstar prefix for
platform classes, file name matches class name (i.e. PSR-4-style). This is the same standard
that authorises the S-03 naming migration.
Blocked inputs (cannot be resolved from this container):
| Input | Status | Impact |
|---|---|---|
Ouroboros v2.0.0 shared-test-vectors.json |
unavailable — lives in the private sparxstar-ouroboros-integrity repo; this session's proxy returns repository not authorized, registries are network-blocked |
Final verdict on the 10 Ouroboros-coupled tests cannot be issued — only the structural triage below |
PAM-003.md |
not a file in this repo — referenced only (in PAM-002.md, TRACKER.md, copilot-instructions.md, and src/core/*) |
PAM-003 acceptance criteria for pulse/trust tests must be supplied to finalise §C |
Where a verdict depends on a blocked input it is marked ⛔ BLOCKED — needs vectors/PAM-003.
| File | Used by | Scope | testVersion |
Distinct rules |
|---|---|---|---|---|
phpcs.xml |
composer phpcs, composer comments:check |
. (whole tree minus excludes) |
8.1–8.4 | NormalizedArrays, ignore-annotations, excludes wp-assets/* & build/*, WordPressVIPMinimum.JS.* excluded |
phpcs.xml.dist |
composer lint (--report=full src/) |
src/* only |
8.2- | PHPCompatibilityParagonie*, explicit installed_paths, WordPress.Files.FileName with strict_class_file_names=false, Generic.Arrays.DisallowLongArraySyntax |
phpcbf.xml.dist |
composer lint:fix, composer phpcbf |
. minus excludes |
— | formatting subset only (no Modernize/Universal/PHPCompatibility/VariableAnalysis) |
Findings:
- Two committed rulesets disagree and are both live.
composer phpcs(386 violations in CI) andcomposer lint(310) scan different paths with different rules — this is the 386-vs-310 discrepancy seen on PR #88. REVIEW → consolidate to one canonical ruleset (recommendphpcs.xml.distas the committed baseline; delete or thinphpcs.xml). phpcs.xml.distis self-contradictory. Its own<description>says "To override locally, create aphpcs.xmlfile (gitignored)" — butphpcs.xmlis committed and is whatcomposer phpcsruns. Either gitignorephpcs.xml(make it a true local override) or delete it and fold any wanted rules into the.dist. REVIEW.testVersionmismatch.phpcs.xml=8.1-8.4,phpcs.xml.dist=8.2-,composer.jsonplatform=8.2.29. The 8.1 lower bound contradicts the 8.2 floor and makes PHPCompatibility flag 8.2-only syntax as errors. REVIEW → set both to8.2-.<arg name="ignore-annotations"/>inphpcs.xml. Inline// phpcs:ignore/phpcs:disableare disabled, so legitimate, reviewed false positives cannot be waived in-line. Combined with the hard-fail gate this guarantees friction. REVIEW → dropignore-annotations(keep waivers reviewable in code) unless the team wants a zero-waiver policy, in which case document it.phpcbf.xml.distfixes a strict subset of whatphpcs.xmlchecks. Afterlint:fix,composer phpcswill still report Modernize/Universal/PHPCompatibility/VariableAnalysis findings that have no autofixer. KEEP (correct by design) — but set expectations: the "386 auto-fixable" PHPCBF banner refers to whitespace/format only; the residue is manual.
Rule ref (in phpcs.xml / .dist) |
Verdict | Rationale |
|---|---|---|
WordPress-Core |
KEEP | Baseline formatting/security; the bulk of the auto-fixable 386 (tabs, paren spacing) come from here and PHPCBF clears them. |
WordPress-Extra |
KEEP w/ carve-outs | Pulls in WordPress.Files.FileName and WordPress.NamingConventions.* — see §A.2 PSR-4 conflicts. |
WordPress-Docs |
REVIEW | Strict docblock sniffs on a namespaced/typed PHP 8.2 codebase produce high-volume, low-value noise; consider scoping to public API only. |
WordPressVIPMinimum |
REVIEW | VIP platform rules on a self-hosted network plugin flag things that don't apply (e.g. restricted functions); phpcbf.xml.dist already excludes several. Decide if VIP is a real target. |
PHPCompatibility / WP / Paragonie* |
KEEP, fix testVersion |
Valuable; only correct once testVersion is unified to 8.2- (finding A.0.3). |
Modernize, Universal, NormalizedArrays (PHPCSExtra) |
KEEP | Aligns with the modern-PHP direction; mostly non-fixable → manual residue. |
VariableAnalysis.CodeAnalysis.VariableAnalysis |
KEEP | Catches real dead/undeclared vars. |
WordPress.NamingConventions.PrefixAllGlobals (sparxstar/star) |
KEEP | Correct prefixes; namespaced symbols are exempt so low conflict. |
WordPress.WP.I18n (text_domain sparxstar) |
KEEP | Correct. Note plugin header text-domain must match sparxstar. |
WordPress.Files.FileName |
EXCLUDE / relax | PSR-4 conflict — see §A.2. Relaxed in .dist, not relaxed in phpcs.xml. |
Generic.Arrays.DisallowLongArraySyntax (.dist only) |
KEEP | Enforces short arrays; consistent with WP-Core. |
The autoloader (composer.json) is PSR-4: Starisian\SparxstarUEC\ and
Starisian\Sparxstar\Sirus\ → src/. Every class file is therefore StudlyCase
(src/core/PulseGenerator.php, src/helpers/IpAnonymizer.php, …). WPCS expects WordPress
file/naming conventions. The specific collisions:
WordPress.Files.FileNamewantsclass-pulsegenerator.php(hyphenated lowercase). PSR-4 requiresPulseGenerator.php.phpcs.xml.distsetsstrict_class_file_names=false(disables the class-matches-file check) but not theNotHyphenatedLowercase/InvalidClassFileNameparts;phpcs.xmldoes not relax it at all → every src class file trips it undercomposer phpcs. Recommend: excludeWordPress.Files.FileNameentirely in the canonical ruleset (incompatible with PSR-4). Per Engineering Standards §6.2 the PSR-4 convention (PascalCase, file matches class) is the mandated standard, so this sniff is excluded by policy, not merely by preference.WordPress.NamingConventions.ValidVariableName(snake_case). Confirmed in CI logs ($actionKey → $action_key). Modern typed code commonly uses camelCase locals; WPCS mandates snake_case. REVIEW — pick one convention and apply repo-wide; if camelCase is the house style, exclude this sniff; if snake_case, it's legitimate debt.- Namespaces vs
PrefixAllGlobals. Low conflict — namespaced symbols are exempt; only truly global symbols need thesparxstar/starprefix. No change.
Net: a large fraction of the 386/310 is convention collision (FileName + camelCase), not latent bugs. Decide PSR-4-vs-WPCS once; the count drops sharply after that plus PHPCBF.
Finding: there is no pre-commit gate of any kind.
| Mechanism checked | Result |
|---|---|
.husky/ directory |
absent |
lint-staged / husky / simple-git-hooks / pre-commit keys in package.json |
none |
.lintstagedrc*, .pre-commit-config.yaml, captainhook, grumphp |
none |
composer.json hook tooling |
none |
.git/hooks/ |
only *.sample (inactive) |
Implications:
- The "phpcbf exit-code bug" (Helios) is N/A here — there is no hook to contain it. For
the record, that bug is: a
pre-commitrunningphpcbftreats PHPCBF's exit code 1 ("fixed some violations") as failure, blocking commits even after a successful auto-fix (PHPCBF returns0= nothing to fix,1= fixed,2= errors remaining; only2is a real failure). If a husky/lint-staged hook is added later, it must treatphpcbfexit1as success (e.g.phpcbf || [ $? -eq 1 ]). - The absence is itself a gate finding: nothing stops a developer committing PHPCS-violating
PHP locally — which is how 386 violations accumulated despite a hard CI gate. REVIEW →
consider a lint-staged
phpcbf(with the exit-code guard) +phpcscheck, but only after §A consolidation, or the hook will inherit the dual-ruleset confusion.
Tests classified valid / outdated→rewrite-to-spec / wrong→delete, decided by spec
(PAM-002, PAM-003, Ouroboros v2.0.0 vectors) — not by current code. 51 test files
(49 unit + 1 integration + SirusTestCase base). Every test maps to a real class or a real
behaviour — no wrong→delete from a missing-class standpoint. The decisive axis is
Ouroboros coupling: 10 tests import the Starisian\Sparxstar\Infrastructure\* namespace
(confirmed external — not defined in src/, i.e. the Ouroboros package), so their contract
is owned by Ouroboros v2.0.0 / PAM-003.
These consume Ouroboros DTOs/utils (ContextPulse, TrustLevelPrimitive,
ContextPulseSigningMaterial, Platform) and must be re-derived from
shared-test-vectors.json, rewritten to spec, not to code:
| Test | Couples to | Note |
|---|---|---|
PulseRoundTripTest |
PulseGenerator + Ouroboros ContextPulse/ContextPulseSigningMaterial/TrustLevelPrimitive |
Highest priority. Round-trip issuance↔verify; directly exercises the PulseGenerator migration (see §C.4). Must assert against v2.0.0 vectors. |
PulseGeneratorTest |
PulseGenerator::generate(), Platform::PULSE_VERSION_CURRENT |
Asserts local issuance shape; if issuance delegates to Ouroboros, this is outdated→rewrite. |
SirusContextTest |
Ouroboros DTOs | Pulse/trust fields → vector-driven. |
AuthorityResolverTest, CapabilityEngineTest, StepUpPolicyTest, TrustResolverTest |
trust-level primitives / token_version |
Trust model is PAM-003 territory. |
ContextEngineTest, ContextCacheTest, IdentityResolverTest, NetworkContextBrokerTest |
context→pulse assembly | Re-verify field set against PAM-003. |
Cannot finalise valid-vs-rewrite without the vectors. Provide
shared-test-vectors.json(and PAM-003 acceptance) to complete this section.
DeviceMatcherTest, DeviceContinuityTest, DeviceRecordTest, SirusDeviceParserTest,
IpAnonymizerTest, EnvironmentRecordTest, EnvironmentResolverTest, ConsentManagerTest,
ClientTelemetryTest, StarLoggerTest, SirusRateLimitTest, SirusRuleConfigTest,
Sirus*RepositoryTest (Event / RuleHit / MitigationAction), SirusEventAggregatorTest,
SirusImpactScorerTest, SirusPriorityScorerTest, SirusSignalEvaluatorTest,
SirusMitigationCoordinatorTest, SirusMitigationRuleEngineTest,
SirusDatabaseEventsTableTest (→ SirusDatabase), SirusNetworkSettingsTest
(→ SirusNetworkSettingsPage), SparxstarUEC*Test (API/CacheHelper/GeoIPService/
SessionManager/InstallerMultisite), StarUserEnv*Test, UECCompatibilityShimTest,
HeliosClientTest, SirusEventControllerTest, SirusDirectiveControllerTest.
→ VALID pending a content read; not Ouroboros-contract-bound.
| Test | Verdict | Note |
|---|---|---|
PluginBootstrapTest |
VALID | Asserts plugin constants (SPX_ENV_CHECK_*) + activation/deactivation hooks registered. Not a missing-class case. |
SirusRESTControllerTest, RestApiTest (integration) |
REVIEW | Reference pulse issuance via new PulseGenerator(); re-verify once §C.4 migration lands. |
SirusTestCase |
base class (not a test) | Shared harness; keep. |
- No namespace/autoload collision. Sirus's
Starisian\Sparxstar\Sirus\core\PulseGeneratoris a distinct FQCN from Ouroboros's class —composer installsucceeded on every PR #88 job with no redeclaration fatal. So at the namespace and autoload levels there is no conflict. - PHPStan level: unverifiable in CI — PHPStan is masked behind the PHPCS hard-fail
(
composer run testshort-circuits), so any type/contract drift between Sirus's local generator and Ouroboros'sPulseGenerator::generate()does not currently surface. - Semantic duplication (the real item). Canonical pulse issuance moved into Ouroboros
(
PulseGenerator::generate()), yet Sirus still issues locally. The generator is instantiated once at the composition root (src/SirusPlugin.php:138,new PulseGenerator()) and constructor-injected into consumers — e.g.SirusRESTController(src/api/SirusRESTController.php:47,private readonly PulseGenerator $pulse_generator), which receives it via DI rather than instantiating it. (Test setups instantiate it directly.) Signing-material format already delegates to Ouroboros (ContextPulseSigningMaterial::build(), CO-001, per the class docblock) — issuance is the un-migrated half. - Left for Max's delegation migration (spec'd item).
PulseRoundTripTest/PulseGeneratorTestare the canaries that will confirm the migration once rewritten to the v2.0.0 vectors.
Actionable now (no spec needed):
- A.0.1/2 consolidate to one ruleset; fix the contradictory
.distdescription. - A.0.3 unify
testVersionto8.2-. - A.0.4 reconsider
ignore-annotations. - A.2.1 exclude
WordPress.Files.FileName(PSR-4 incompatible). - A.2.2 decide camelCase vs snake_case repo-wide.
- B add a lint-staged
phpcbfhook with the exit-code-1 guard (after A consolidation).
Blocked — need inputs to finalise:
- Ouroboros v2.0.0
shared-test-vectors.json→ finalise §C.1 verdicts. - PAM-003 acceptance criteria → trust/pulse test rewrites.
TRACKER.md refresh remains a separate follow-up — it records verified state only, so it should land after the gate is consolidated and CI is trustworthy (i.e. after §A/§B are applied and §C is unblocked by the vectors).