PHP Language Analyzer
Contract ID:
CONTRACT-SCANNER-PHP-ANALYZER-013Status: Published · Version: 1.0.0 · Published: 2025-12-05 Owners: PHP Analyzer Guild, Scanner Guild
Reconciliation note (2026-05-30): This contract was originally drafted as a bootstrap design. The PHP analyzer has since shipped, and several sections described aspirational shapes that differ from the implementation. The document has been reconciled against the source at
src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/. Sections that describe behaviour not yet present in source are marked (Roadmap — not yet implemented). The C# type signatures below mirror the actualinternaltypes unless explicitly flagged as illustrative.
Overview
This contract defines the Stella Ops PHP language analyzer — the Scanner component that inventories a PHP project’s dependencies and attack surface. It covers Composer manifest/lock parsing, virtual file system (VFS) normalization, framework/CMS fingerprinting, runtime capability and PHAR/FFI scanning, and the language-component records the analyzer emits for deterministic, offline PHP project analysis.
Audience: engineers maintaining or extending the PHP analyzer, and Scanner contributors consuming its language-component output downstream (emit, SBOM serialization, reachability).
The implemented entry point is PhpLanguageAnalyzer (analyzer id php, display name PHP Analyzer), which implements IScannerLanguageAnalyzer and is activated through the PhpScannerAnalyzerPlugin plug-in (IScannerLanguageAnalyzerPlugin).
Scope
The PHP analyzer:
- Parses
composer.json(PhpComposerManifestReader) andcomposer.lock(ComposerLockReader) - Builds an in-memory virtual file system from the scan root, vendor directory, and configs (
PhpInputNormalizer) - Detects framework/CMS fingerprints (Laravel, Symfony, WordPress, Drupal, and 13 others) via
PhpFrameworkFingerprinter - Emits language-component records with composer PURLs plus dotted metadata keys
- Scans for autoload edges, include/require graphs, runtime capabilities, PHAR archives, FFI usage, framework surface, PHP config/extensions, and version conflicts
Note on output format: The analyzer emits Stella Ops language-component records (consumed by the Scanner emit pipeline), not CycloneDX SBOM documents directly. See Output Schema. CycloneDX/SPDX serialization is a downstream concern of
StellaOps.Scanner.Emit.
Input Normalization
PhpInputNormalizer.NormalizeAsync(rootPath, ct) builds a PhpProjectInput from the scan root. It does not merge fixed mount points such as /app, /vendor, or /etc/php*; it recursively enumerates the supplied rootPath.
File Discovery (implemented)
The normalizer enumerates the root tree and classifies files by source into a PhpVirtualFileSystem. There is no IPhpSourceDiscovery interface or PhpDiscoveryOptions record in source. Discovery behaviour is fixed:
- PHP source extensions collected:
.php,.phtml,.inc,.module(thevendor/tree is enumerated separately and collects only.phpfiles plusvendor/composer/installed.json). - Excluded directories:
.git,.svn,.hg,node_modules,.idea,.vscode. - Config files collected:
.htaccess,php.ini(andphp/php.ini,etc/php.ini),conf.d/*.inivariants, and PHP-FPM*.conffiles. - Composer manifests collected:
composer.json,composer.lock.
Each collected file is tagged with a PhpFileSource (see File Sources).
(Roadmap — not yet implemented) A configurable discovery surface (include/exclude globs, optional vendor/test inclusion) is not present. The
IPhpSourceDiscovery/PhpDiscoveryOptionsAPI and the default exclude patterns (*.min.php,cache/*) do not exist in source.
File Sources
internal enum PhpFileSource
{
SourceTree, // application code
Vendor, // composer vendor directory
ContainerLayer, // OCI/Docker layer (enum value present; not yet populated)
PhpConfig, // php.ini, conf.d
WebServerConfig, //.htaccess
FpmConfig, // php-fpm.conf / pool configs
ComposerManifest // composer.json / composer.lock
}
Composer Schema
composer.json Parsing
PhpComposerManifestReader.LoadAsync parses composer.json into a PhpComposerManifest with: Name, Description, Type, Version, License, Authors, Require, RequireDev, Autoload, AutoloadDev, Scripts, Bin, MinimumStability, and Sha256 (SHA-256 of the manifest file). Computed views include RequiredPhpVersion (the php constraint in require) and RequiredExtensions (the ext-* keys).
{
"name": "vendor/package",
"version": "1.2.3",
"type": "library|project|metapackage|composer-plugin",
"require": {
"php": ">=8.1",
"vendor/dependency": "^2.0"
},
"require-dev": { },
"autoload": {
"psr-4": { "App\\": "src/" },
"classmap": ["database/"],
"files": ["helpers.php"]
}
}
composer.lock Parsing
ComposerLockReader extracts the lockfile-level content-hash and plugin-api-version, plus per-package metadata into ComposerPackage records. The lock data carries a LockSha256 (SHA-256 of the composer.lock file itself, lowercase hex), used as the package evidence hash.
internal sealed record ComposerPackage(string Name,
string Version,
string? Type, // library, composer-plugin, etc.
bool IsDev, // from "packages-dev"
string? SourceType, // source.type: git|hg|svn
string? SourceReference, // source.reference (commit/tag)
string? DistSha256, // dist.shasum (falls back to dist.checksum)
string? DistUrl, // dist.url
ComposerAutoloadData Autoload);
Field reconciliation: The lockfile
content-hashis a composer integrity hash over the root requirements, not a SHA-256 of package contents. The per-packagedist.shasum(exposed asDistSha256) is the package archive checksum. TheComposerLockPackagerecord (with a singleContentHash = "SHA256 of package contents") did not match either field and has been replaced with the actualComposerPackageshape above.
ComposerLockData holds: LockPath, ContentHash, PluginApiVersion, Packages, DevPackages, LockSha256.
Autoload Data
internal sealed class ComposerAutoloadData
{
IReadOnlyList<string> Psr4; // entries serialized as "Namespace\->path"
IReadOnlyList<string> Classmap;
IReadOnlyList<string> Files;
}
Note: PSR-0 is not parsed by the lock reader (only
psr-4,classmap,files). The autoload graph builder (PhpAutoloadGraphBuilder) does emit aautoload.psr0_countmetadata key, sourced separately frominstalled.json.
PURL Format
PhpPackage.Purl produces:
pkg:composer/vendor/package@version
pkg:composer/laravel/framework@10.48.7
pkg:composer/symfony/http-kernel@6.3.0
The component key for a package is purl::pkg:composer/<vendor>/<package>@<version>.
Virtual File System
Model (implemented)
The VFS is the internal type PhpVirtualFileSystem, built via a Builder. It is not a public record and does not carry a ContentHash of its own.
internal sealed class PhpVirtualFileSystem
{
IReadOnlyList<PhpVirtualFile> Files { get; } // deterministically ordered
int Count { get; }
bool TryGetFile(string relativePath, out PhpVirtualFile? file);
IEnumerable<PhpVirtualFile> GetFilesBySource(PhpFileSource source);
IEnumerable<PhpVirtualFile> GetFilesByPattern(string pattern); // simple glob
IEnumerable<PhpVirtualFile> GetPhpFiles();
}
internal sealed record PhpVirtualFile
{
string RelativePath { get; } // normalized to forward slashes
string AbsolutePath { get; }
PhpFileSource Source { get; }
string? Sha256 { get; init; } // optional; not populated during normalization
long? Size { get; init; } // optional
IReadOnlyDictionary<string, string>? Metadata { get; init; }
}
Reconciliation: The public
PhpVirtualFileSystemrecord (withRootPath,Entries,PhpConfig,Composer, BLAKE3ContentHash), theVfsEntryrecord, and theVfsEntryTypeenum do not exist in source. There is no BLAKE3 hashing; per-file hashing uses SHA-256 and is optional. The project-level container isPhpProjectInput(RootPath, FileSystem, Config, Framework, ComposerLock, ComposerManifest).
Deterministic Ordering
PhpVirtualFileSystem.Builder.Build() sorts files by RelativePath using StringComparer.Ordinal (case-sensitive, lexicographic). Duplicate relative paths are de-duplicated case-insensitively, with the last AddFile winning.
Framework Detection
Fingerprint Rules (implemented)
PhpFrameworkFingerprinter ships 17 framework/CMS definitions. Each definition scores by the fraction of its file markers that exist on disk (with a small boost when 3+ markers match); the highest-scoring candidate wins. When no on-disk match is found, DetectFromPackages falls back to composer package markers (fixed confidence 0.95). Confidence is a float in [0.0, 1.0], not a High/Medium/Low enum — there is no ConfidenceLevel type.
| Framework | Selected file markers (any-of, scored) | Composer marker |
|---|---|---|
| Laravel | artisan, bootstrap/app.php, config/app.php, routes/web.php, app/Http/Kernel.php | laravel/framework |
| Symfony | bin/console, config/bundles.php, src/Kernel.php, symfony.lock | symfony/framework-bundle |
| WordPress | wp-config.php, wp-includes/version.php, wp-admin/admin.php, wp-content/themes, wp-content/plugins | — |
| Drupal | core/lib/Drupal.php, sites/default/settings.php, core/modules, modules/contrib | drupal/core |
| Joomla | configuration.php, libraries/src/Version.php, administrator/index.php, components | — |
| Magento | app/Mage.php, app/etc/config.php, app/etc/env.php, pub/index.php | magento/framework |
| CodeIgniter | system/CodeIgniter.php, app/Config/App.php, spark | codeigniter4/framework |
| CakePHP | bin/cake, config/app.php, src/Application.php | cakephp/cakephp |
| Slim | public/index.php | slim/slim |
| Laminas | config/application.config.php, module/Application | laminas/laminas-mvc |
| Yii | yii, config/web.php, controllers, views | yiisoft/yii2 |
| MediaWiki | LocalSettings.php, includes/DefaultSettings.php, skins/Vector | — |
| phpBB | config.php, phpbb/di/container_builder.php, styles/prosilver | — |
| Craft | craft, config/general.php, templates | craftcms/cms |
| TYPO3 | typo3/sysext, typo3conf/LocalConfiguration.php | typo3/cms-core |
| October | artisan, modules/system, plugins | october/rain |
| PrestaShop | config/settings.inc.php, classes/PrestaShopAutoload.php, modules, themes | — |
Version extraction (when a VersionFile + regex is defined) reads a marker file such as wp-includes/version.php ($wp_version) or vendor/.../Application.php (const VERSION).
Reconciliation of prior table: The earlier table claimed “Magento detected via
Magento\namespace” and “CodeIgniter viasystem/core/CodeIgniter.php” — the implementation uses file-path markers only (app/etc/env.php,system/CodeIgniter.php). Confidence is a numeric score in source.
Fingerprint Output (implemented)
internal sealed class PhpFrameworkFingerprint
{
PhpFrameworkKind Kind { get; } // enum: None, Laravel,... Custom
string Name { get; } // "laravel", "symfony",... ("unknown" when none)
string? Version { get; }
float Confidence { get; } // 0.0.. 1.0
IReadOnlyList<string> Evidence { get; } // contributing markers, ordinal-sorted
bool IsDetected { get; } // Kind != None
}
Reconciliation: The prior
PhpFrameworkFingerprintrecord usedConfidenceLevel ConfidenceandIndicatorFiles; the real type uses afloatConfidence, anEvidencelist, and addsKind/IsDetected.
Framework signals surface in metadata as php.framework.kind, php.framework.name, php.framework.version, php.framework.confidence (formatted F2), and php.framework.evidence (;-joined), plus the project-level php.project.framework / php.project.framework_version.
PHP Configuration
Config File Discovery (implemented)
PhpConfigCollector.CollectAsync probes (relative to the scan root):
php.ini, php/php.ini, etc/php.ini, etc/php/php.ini, usr/local/etc/php/php.ini
conf.d/, php/conf.d/, etc/php/conf.d/, etc/php.d/, usr/local/etc/php/conf.d/ (*.ini)
.htaccess
php-fpm.conf, php-fpm.d/, etc/php-fpm.conf, etc/php-fpm.d/,
etc/php/fpm/php-fpm.conf, etc/php/fpm/pool.d/, usr/local/etc/php-fpm.conf, usr/local/etc/php-fpm.d/
Reconciliation: The prior absolute-path list (
/etc/php/*/php.ini,/etc/php/*/conf.d/*.ini,/etc/php-fpm.d/*.conf,/usr/local/etc/php/php.ini) used a different layout; the collector resolves paths relative to the scan root, not absolute container paths.
Security-Relevant Settings (implemented)
Settings are surfaced by PhpExtensionScanner into PhpEnvironmentSettings, which groups them into PhpSecuritySettings, PhpUploadSettings, PhpSessionSettings, PhpErrorSettings, and PhpResourceLimits. There is no single PhpSecurityConfig record.
internal sealed record PhpSecuritySettings(IReadOnlyList<string> DisabledFunctions,
IReadOnlyList<string> DisabledClasses,
bool OpenBasedir,
string? OpenBasedirValue,
bool AllowUrlFopen,
bool AllowUrlInclude,
bool ExposePhp,
bool RegisterGlobals);
display_errors / display_startup_errors / log_errors live in PhpErrorSettings (emitted as error.display_errors, etc.). Security metadata keys include security.allow_url_fopen, security.allow_url_include, security.open_basedir, security.expose_php, security.disabled_functions[_count], and security.disabled_classes_count.
Reconciliation: The prior
PhpSecurityConfigrecord (withOpenBasedir/DisableFunctions/DisableClasses/DisplayErrorsas a single bag) does not exist. Disable lists areDisabledFunctions/DisabledClassesonPhpSecuritySettings;DisplayErrorsis onPhpErrorSettings.
Output Schema
The analyzer writes language-component records through a ScannerComponentSinkWriter. Each record carries analyzerId (php), componentKey, optional purl, name, version, type, usedByEntrypoint, a flat metadata string→string map (dotted keys), and an evidence array.
Package Component (actual emitted shape)
{
"analyzerId": "php",
"componentKey": "purl::pkg:composer/laravel/framework@10.48.7",
"purl": "pkg:composer/laravel/framework@10.48.7",
"name": "laravel/framework",
"version": "10.48.7",
"type": "composer",
"usedByEntrypoint": false,
"metadata": {
"composer.dev": "false",
"composer.type": "library",
"composer.source.type": "git",
"composer.source.ref": "0123...4567",
"composer.autoload.psr4": "Illuminate\\->src/Illuminate;...",
"composer.autoload.files": "src/Illuminate/Foundation/helpers.php",
"composer.dist.sha256": "6f1b...",
"composer.dist.url": "https://api.github.com/repos/laravel/framework/zipball/...",
"composer.plugin_api_version": "2.6.0",
"composer.content_hash": "e01f9b7d...",
"php.capability.framework": "laravel"
},
"evidence": [
{
"kind": "file",
"source": "composer.lock",
"locator": "composer.lock",
"value": "laravel/framework@10.48.7",
"sha256": "885d825c..."
}
]
}
Reconciliation: The earlier “SBOM Component” example used CycloneDX shapes (
bom-ref,properties[]withstellaops:php:*names, and anevidence.identityblock withmethods/technique). The analyzer does not emit CycloneDX directly, does not usestellaops:php:*property names (it uses dotted keys likecomposer.*andphp.*), and usesusedByEntrypoint+ a flat evidence array (kind/source/locator/value/sha256). CycloneDX/SPDX serialization happens later inStellaOps.Scanner.Emit.
Component Types Emitted
In addition to per-package composer components, PhpLanguageAnalyzer emits:
| Type | Component key | Purpose |
|---|---|---|
composer | purl::pkg:composer/<v>/<p>@<ver> | One per locked package (packages + packages-dev) |
php-project | php::project-summary | Aggregated project metadata (autoload/include/capability/phar/surface/env/ffi/conflict summaries) |
php-bin | php::bin::<name> | Composer bin entrypoints |
php-plugin | php::plugin::<package> | Composer plugins (classes implementing the plugin API) |
php-phar | php::phar::<relativePath> | Discovered PHAR archives |
Project-Summary Metadata Keys (implemented)
The php-project summary aggregates dotted-key metadata across the analysis passes. Observed key prefixes (from golden fixtures):
php.project.*—file_count,uses_composer,has_vendor,framework,framework_versioncomposer.manifest.*—name,type,license,php_version,extensions,require_count,require_dev_count,sha256autoload.*—psr4_count,psr0_count,classmap_count,files_count,edge_count,bin_count,plugin_countinclude.*—edge_count,static_count,dynamic_count,include_count,require_count,bootstrap_chain_countcapability.*—has_exec,has_network,has_filesystem,has_crypto,has_database,has_dynamic_code,has_serialization,has_reflection,has_stream_wrapper,has_upload,has_session,has_environment, risk counts,total_count,unique_function_countphar.*—archive_count,usage_count,archives_with_vendor,total_archived_filessurface.*—route_count,controller_count,middleware_count,cli_command_count,cron_job_count,event_listener_count,public_routes,protected_routes,http_methodsenv.*—extension_count,extensions_<category>(e.g.extensions_core,extensions_crypto)security.*,upload.*,session.*,error.*,limits.*— PHP config settings (see PHP Configuration)ffi.*—detected,enabled_setting,usage_countconflict.*—detected,countphp.config.entry_count
(Completeness gap closed) The capability/PHAR/FFI/surface/include/conflict scanning passes (
PhpCapabilityScanner,PhpPharScanner,PhpFfiDetector,PhpFrameworkSurfaceScanner,PhpIncludeGraphBuilder,PhpVersionConflictDetector,PhpExtensionScanner) were absent from the original contract. They are a substantial part of the shipped analyzer output.
Analysis Store Keys
(Roadmap — not implemented) There is no
PhpAnalysisKeysstatic class in source. The analyzer does not persist named analysis-store entries; it emits language-component records (above) via the component sink. The constants (php.vfs,php.composer,php.frameworks,php.security-config,php.autoload) do not exist.
Offline Kit Target
(Roadmap — not yet implemented) No offline-kit bundle, Packagist mirror, or offline composer repository exists in source. There is no
IOfflineComposerRepositoryinterface orComposerPackage/ComposerManifestoffline-resolution API. The analyzer is already offline-friendly because it reads only localcomposer.lock/composer.json/vendor/content and never contacts a remote registry, but the bundle layout below is aspirational.
offline/php-analyzer/ # PLANNED — not produced today
├── manifests/
│ └── php-analyzer-manifest.json
├── fixtures/
│ ├── laravel-app/
│ ├── symfony-app/
│ └── wordpress-site/
├── vendor-cache/
│ └── packages.json
└── SHA256SUMS
Test Fixtures
Actual Fixtures (implemented)
Golden fixtures live under src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests/Fixtures/lang/php/. Each fixture directory contains a composer.lock and a single expected.json (the full ordered array of emitted language-component records). The fixtures are:
| Fixture | Coverage |
|---|---|
basic/ | Minimal composer project (one runtime + one dev package) |
container/ | Container-layer style layout |
laravel-extended/ | Laravel framework signals |
legacy/ | Older/legacy PHP project |
phar/ | PHAR archive discovery |
symfony/ | Symfony framework signals |
wordpress/ | WordPress CMS signals |
Reconciliation: The prior fixture list (
laravel-10-app/,symfony-6-app/,wordpress-6-site/,drupal-10-site/,composer-only/,legacy-php56/) and the per-fixtureEXPECTED.sbom.json/EXPECTED.vfs.json/EXPECTED.meta.jsongolden split do not match the source. Actual fixtures use a singleexpected.jsongolden pluscomposer.lock. No Drupal or PHP-5.6-specific fixture exists today.
Golden Output Format (actual)
Fixtures/lang/php/<name>/
├── composer.lock
└── expected.json # ordered array of emitted language-component records
Determinism is enforced by PhpLanguageAnalyzerTests comparing emitted records against expected.json.
Implementation Status
The analyzer is implemented (manifest org.stellaops.analyzer.status: production, plug-in version 0.1.0). The original phased plan is recorded below for history; all three phases have effectively shipped, with offline-kit bundling remaining as roadmap (see Offline Kit Target).
Phase 1: Core Parser — DONE
PhpComposerManifestReader(composer.json) — implementedComposerLockReader(composer.lock) — implemented- PURL generation (
PhpPackage.Purl) — implemented - VFS construction (
PhpInputNormalizer+PhpVirtualFileSystem) — implemented
Phase 2: Framework Detection — DONE
- Fingerprint rules (
PhpFrameworkFingerprinter, 17 definitions) — implemented - Float confidence scoring — implemented
- Version extraction via marker-file regex — implemented
Phase 3: Offline Support — PARTIAL
- Vendor/lockfile-only reads (offline by construction) — implemented
- Offline composer repository — not implemented (roadmap)
- Offline-kit bundle generation — not implemented (roadmap)
Project Location
The implemented analyzer lives under __Libraries, not the top-level path. The top-level src/Scanner/StellaOps.Scanner.Analyzers.Lang.Php/ directory currently contains only AGENTS.md (guild charter).
src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/
├── StellaOps.Scanner.Analyzers.Lang.Php.csproj
├── PhpLanguageAnalyzer.cs # IScannerLanguageAnalyzer (id "php")
├── PhpScannerAnalyzerPlugin.cs # IScannerLanguageAnalyzerPlugin
├── PhpScannerSemanticEnricherPlugin.cs # IScannerSemanticEnricherPlugin
├── GlobalUsings.cs
├── manifest.json # plug-in manifest
└── Internal/
├── PhpInputNormalizer.cs
├── PhpVirtualFileSystem.cs / PhpVirtualFile.cs
├── PhpProjectInput.cs
├── PhpComposerManifest.cs / PhpComposerManifestReader.cs
├── ComposerLockData.cs / ComposerLockReader.cs / ComposerPackage.cs
├── ComposerAutoloadData.cs / PhpAutoloadGraphBuilder.cs / PhpAutoloadEdge.cs
├── PhpFrameworkFingerprint.cs / PhpFrameworkFingerprinter.cs
├── PhpFrameworkSurface.cs / PhpFrameworkSurfaceScanner.cs
├── PhpCapability*.cs (Evidence/ScanBuilder/ScanResult/Scanner/Signals)
├── PhpPharArchive.cs / PhpPharScanner.cs
├── PhpFfiDetector.cs
├── PhpInclude*.cs (Edge/GraphBuilder/Scanner)
├── PhpConfig*.cs (Collection/Collector/Entry)
├── PhpExtension.cs / PhpExtensionScanner.cs
├── PhpInstalledJsonReader.cs
├── PhpPackage.cs / PhpPackageCollector.cs
├── PhpVersionConflictDetector.cs
└── Runtime/
Reconciliation: The prior file list (
ComposerJsonParser.cs,ComposerLockParser.cs,PhpVirtualFileSystem.csat root,PhpFrameworkDetector.cs,PhpAnalyzerPlugin.cs,README.md) did not match source. Actual readers arePhpComposerManifestReader/ComposerLockReader; the fingerprinter isPhpFrameworkFingerprinter; the plug-in isPhpScannerAnalyzerPlugin; there is noREADME.mdin the project.
Plug-in identity (implemented)
| Field | Value |
|---|---|
| Manifest id | stellaops.analyzer.lang.php |
Plug-in (IScannerLanguageAnalyzerPlugin.Id) | stellaops.scanner.plugin.php |
| Enricher plug-in id | stellaops.scanner.plugin.php.enricher |
Analyzer id (IScannerLanguageAnalyzer.Id) | php |
| Version | 0.1.0 |
requiresRestart | true |
| Capabilities | language-analyzer, php, composer |
Source defect flagged (not corrected here — doc-only change):
manifest.jsondeclaresentryPoint.typeName = "StellaOps.Scanner.Analyzers.Lang.Php.PhpAnalyzerPlugin", but noPhpAnalyzerPlugintype exists; the actual plug-in type isPhpScannerAnalyzerPlugin. This is a source/manifest mismatch that the PHP Analyzer Guild should reconcile in code (it is outside the scope of this documentation edit).
Related Contracts
- RichGraph v1 — reachability graph schema; defines the PHP SymbolID/CodeID tuple this analyzer’s symbols map onto.
- Scanner Surface — attack-surface analysis framework (PHP entry-point discovery is on its roadmap).
Changelog
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0.0 | 2025-12-05 | PHP Analyzer Guild | Initial contract |
| 1.0.0 (reconciled) | 2026-05-30 | Docs reconciliation | Aligned C# signatures, framework table (17 frameworks, float confidence), composer lock/package shapes, VFS model, PHP config types, emitted component types and metadata keys, fixtures, and project location with shipped source; marked offline-kit, IPhpSourceDiscovery/PhpDiscoveryOptions, PhpAnalysisKeys, IOfflineComposerRepository, BLAKE3/VfsEntry/VfsEntryType, and CycloneDX-shaped output as not-implemented or roadmap; flagged the manifest.json typeName defect |
