test: record Drupal profile and convergence methodology

This commit is contained in:
Forge
2026-09-22 13:20:29 +00:00
parent e72c064546
commit 2bc4029bf4
5 changed files with 56 additions and 9 deletions
+3
View File
@@ -17,6 +17,9 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Extension preview
- Prepare immutable Drupal extension 0.1.1: ignore unrelated static receivers and commented/quoted declarations, and resolve both explicit Symfony event argument orders. Preserve the reviewed 0.1.0 artifact and policy.
- Keep installed and selected marketplace versions distinct while compatibility metadata refreshes; pending requests no longer display an incompatible catalog version as installed.
- Add project-scoped framework and semantic extensions, managed package installation, worker loading, and graph refresh on install/update/enable/disable/remove. Failed activation preserves the previous project configuration and graph.
- Add an external Drupal extension for declared routes/forms, services, hooks, plugins and event subscribers. Literal route paths can be explore endpoints; extension edges retain their labels and registration sites. Comments and quoted examples do not count as PHP invocations.
- Add a local marketplace preview with signed publisher submissions, immutable releases, publisher ownership checks and a companion connection for project selection. Mobile detail pages retain version, compatibility and source information. Hosted deployment remains follow-up work; this is not a release announcement.
+20 -5
View File
@@ -4,11 +4,26 @@
queries in [Checking this file is still true](#checking-this-file-is-still-true)
before trusting a row; this is a snapshot, not a live view.
**Extension preview, 2026-09-22:** the optional external Drupal package adds
declared route/form handlers, service injection, literal hook/event dispatch
and plugin implementation links. It does not add Drupal screen navigation or
PHP branch guards. Scope and reproducible validation are recorded in
[the extension recovery report](../validation/extensions-recovery-20260922.md).
**Extension preview, 2026-09-22:** Drupal 0.1.1 supports explicit `_controller`
and `_form` route defaults, unique YAML service classes/aliases and literal
constructor arguments, literal service lookups, attached hook documentation or
`Hook` attributes, explicit plugin IDs from attached annotations/attributes,
and named event dispatch/subscriber links. Both Symfony `dispatch(name, event)`
and `dispatch(event, name)` forms require an explicit literal/constant name.
Commented calls, quoted declaration examples, unrelated `NotDrupal::service`,
unknown targets, cyclic service aliases, computed IDs and duplicate plugin IDs
are covered by negative checks.
The review samples 28 exact links across pinned Pathauto, Commerce and Drupal
core, plus four unresolved corpus cases and focused synthetic positives/negatives.
It is not an exhaustive precision/recall measurement. Dynamic entity forms,
container factories/decorators, autowiring, computed dispatch names, single-argument
object event inference, grouped imports and multiple namespaces per file are not
certified. Receiver matching for hook/event/plugin APIs remains heuristic, not
PHP runtime type analysis. No Drupal screen navigation or PHP branch guards are
added. Immutable 0.1.0 is retained alongside 0.1.1. See the
[Drupal review report](../validation/extensions-drupal-review-20260922.md) for
exact source, measurements, exclusions and remaining limits.
This file exists to be read cold. It says, for every framework and language the
README claims, **which of the three pictures it can draw today** and what is
+10 -3
View File
@@ -4,9 +4,16 @@ The Cloudflare registry's public `/api/publish` endpoint cannot assign official
## Trust and immutable publication
`marketplace/cloudflare/official-policy.json` is a reviewed allowlist, committed alongside the operator. It pins Drupal `0.1.0`, package name, exact 289,167-byte artifact SHA-256, bundled entry hash, source repository/revision/path/hash and official metadata. The trusted identity is `publisherId: codegraph`, display name `CodeGraph`; it is distinct from the 64-hex SPKI identities assigned by community signatures. The policy is an operator trust decision anchored in reviewed source, **not a claim of a new signed upstream release**. Changing the policy requires review and a new immutable version when bytes change.
`marketplace/cloudflare/official-policy.json` is a reviewed allowlist, committed alongside the operator. It retains Drupal `0.1.0` unchanged and adds the reviewed `0.1.1` accuracy revision. Each entry pins package name, version, byte length, artifact SHA-256, bundled entry hash, source repository/revision/path/hash and official metadata. The trusted identity is `publisherId: codegraph`, display name `CodeGraph`; it is distinct from the 64-hex SPKI identities assigned by community signatures. The policy is an operator trust decision anchored in reviewed source, **not a claim of a new signed upstream release**. Changing the policy requires review and a new immutable version when bytes change.
Artifact provenance: `dist/extensions/drupal.cgext` SHA-256 `e5015747d02fe50d6af607b6c44f1db9cf7969a0070866ed03c7643377a459c8`; source `extensions/drupal/index.cjs` at `37d4dd837120c1d56316c3cc65b6587b743a9112`. The existing `node scripts/build-extensions.mjs` build is reproducible with repository-locked dependencies. The command validates the complete artifact, supported extension API and the target engine version before any write, then enforces the pinned identity/integrity/entry provenance.
Artifact provenance:
| Version | Artifact | SHA-256 | Reviewed source |
|---|---|---|---|
| 0.1.0 | `dist/extensions/drupal-0.1.0.cgext` (289,167 bytes) | `e5015747d02fe50d6af607b6c44f1db9cf7969a0070866ed03c7643377a459c8` | `37d4dd837120c1d56316c3cc65b6587b743a9112` |
| 0.1.1 | `dist/extensions/drupal-0.1.1.cgext` (291,889 bytes); also current `drupal.cgext` | `e5ed73bee51524585e4f4da1d5017fdee3037a3fa71d8f08edab6c318fedc59a` | `b27bf875c95d3b82a0df411f68d0d8497993667b` |
The build preserves the archived 0.1.0 package under `extensions/drupal/releases/` and copies it byte-for-byte. `node scripts/build-extensions.mjs` builds 0.1.1 with repository-locked dependencies. The command validates the complete artifact, supported extension API and target engine before any write, then enforces pinned identity/integrity/entry provenance. Publish 0.1.1 as a new version; never replace 0.1.0. Both can coexist and repeated identical imports are idempotent.
Each plan includes an explicit registry identity and destination. `apply` requires the SHA-256 of the exact reviewed plan and validates its fields against the policy again. A different registry, unsupported engine, malformed artifact, changed plan, unexpected package or digest mismatch fails without publication. Plans and receipts contain public metadata and paths; credentials are never placed in either.
@@ -80,4 +87,4 @@ PLAYWRIGHT_MODULE=/absolute/path/to/playwright PATHAUTO_CORPUS=/absolute/pinned/
The browser test exports clean Pathauto source at `b97aadf47a37cff25f7d6105c3dade6778fccb47` into a disposable project. It uses the real CLI and local workerd/D1/R2, connects the browser once, clicks Install once, and verifies two route handlers, one hook and one service injection in actual SQLite. It proves malformed/incompatible/tampered failure preservation, disable/enable/remove cleanup, official catalog/detail/filter, and desktop/mobile layout. It does not modify the source corpus or rerun the larger corpus benchmark.
[Hosting/cost limits](cloudflare-hosting.md) remain unchanged: no durable Cloudflare URL, provider persistence, remote backup proof or Free-plan CPU fit is established. `marketplace.getcodegraph.com` remains proposed. Broader Drupal accuracy/performance review and current integrated full regression remain later milestones; historical measured overhead is 22–30%.
[Hosting/cost limits](cloudflare-hosting.md) remain unchanged: no durable Cloudflare URL, provider persistence, remote backup proof or Free-plan CPU fit is established. `marketplace.getcodegraph.com` remains proposed. The Drupal review evidence is recorded separately; historical measured rebuild overhead was 22–30%, and local rebuild timings do not establish Workers Free CPU suitability.
@@ -0,0 +1,22 @@
// Isolate the two versioned wiring callbacks over one identical, read-only
// node/source snapshot. This is not a whole-index or graph-equivalence test.
const fs=require('node:fs'),path=require('node:path'),os=require('node:os'),assert=require('node:assert/strict'),crypto=require('node:crypto');
const {DatabaseSync}=require('node:sqlite');const {QueryBuilder}=require('../../dist/db/queries');
const [project,output]=process.argv.slice(2),scratch=fs.mkdtempSync(path.join(os.tmpdir(),'drupal-pass-profile-'));
const hash=b=>crypto.createHash('sha256').update(b).digest('hex');
(async()=>{const db=new DatabaseSync(path.join(project,'.codegraph/codegraph.db'),{readOnly:true});
try{
const q=new QueryBuilder(db),all=q.getAllNodes(),byFile=new Map(),byName=new Map();
for(const n of all){for(const [map,key]of [[byFile,n.filePath],[byName,n.name]]){if(!map.has(key))map.set(key,[]);map.get(key).push(n);}}
const files=db.prepare('SELECT path FROM files ORDER BY path').all().map(f=>f.path),sources=new Map(files.map(f=>[f,fs.readFileSync(path.join(project,f),'utf8')]));
const ctx={getAllFiles:()=>files,getNodesInFile:f=>byFile.get(f)||[],getNodesByName:n=>byName.get(n)||[],readFile:f=>sources.get(f)||null};
const passes={},artifacts={};for(const v of ['0.1.0','0.1.1']){const bytes=fs.readFileSync(`dist/extensions/drupal-${v}.cgext`),p=JSON.parse(bytes),file=path.join(scratch,v+'.cjs');fs.writeFileSync(file,p.files[p.package.main]);passes[v]=require(file)().synthPasses[0];artifacts[v]=hash(bytes);}
const record={project,files:files.length,nodes:all.length,artifacts,description:'Warm in-memory same snapshot; callback only; changed versions have intentionally different results',samples:[]};
for(let iteration=1;iteration<=3;iteration++)for(const version of (iteration%2?['0.1.0','0.1.1']:['0.1.1','0.1.0'])){
const cpu=process.cpuUsage(),start=performance.now();const edges=await passes[version].run(ctx,()=>new Promise(resolve=>setImmediate(resolve)));
const sample={iteration,version,wallMs:performance.now()-start,cpu:process.cpuUsage(cpu),edges:edges.length,hash:hash(JSON.stringify(edges))};record.samples.push(sample);console.log(sample);
const prior=record.samples.find(s=>s.version===version&&s!==sample);if(prior)assert.equal(sample.hash,prior.hash);
fs.mkdirSync(path.dirname(output),{recursive:true});fs.writeFileSync(output,JSON.stringify(record,null,2));
}
}finally{db.close();fs.rmSync(scratch,{recursive:true,force:true});}
})().catch(e=>{console.error(e);process.exitCode=1;});
@@ -15,7 +15,7 @@ function snapshot(){const db=new DatabaseSync(path.join(project,'.codegraph/code
for(const file of files)assert.ok(!fs.existsSync(path.join(project,file)));
fs.writeFileSync(path.join(project,files[0]),`<?php
namespace Drupal\\review;
class ReviewSubscriber { public static function getSubscribedEvents() { return ['review.convergence' => 'onReview']; } public function onReview($event) {} }
class ReviewSubscriber implements \\Symfony\\Component\\EventDispatcher\\EventSubscriberInterface { public static function getSubscribedEvents() { return ['review.convergence' => 'onReview']; } public function onReview($event) {} }
class ReviewDispatch { public function send($dispatcher,$event) { $dispatcher->dispatch('review.convergence', $event); } }
`);
fs.writeFileSync(path.join(project,files[1]),"review.route:\n path: '/__review_unknown'\n defaults:\n _controller: '\\Missing\\Unknown::missing'\n");