backend-skeleton 1.0.0-beta.7 → 1.0.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -104,6 +104,22 @@ function writeUnit(target, content) {
104
104
  fs.writeFileSync(target, content);
105
105
  }
106
106
 
107
+ // O3 follow-up (D-handle-registry-enforcement, "Continued"): a coarse, per-resource, emit-time,
108
+ // source-only proxy for "has this resource's own create-flow ever been wired to register a
109
+ // HandleRegistry row" -- see DECISIONS.md for why this stays static and never queries a live
110
+ // target-app database (that's a separate, harder, still-open gap, not this). Requires the opening
111
+ // "(" so a bare comment/javadoc mention of the annotation's name (RecordHandleSnapshot.java.tmpl's
112
+ // own javadoc has one) doesn't count as "found". Deliberately biased toward a false-positive
113
+ // WARNING (nagging a resource that's actually registered some other way, e.g. a hand-written
114
+ // HandleService.register() call with no annotation) over a false-negative SILENCE (saying nothing
115
+ // about a resource that really can never bootstrap its first PATCH) -- see DECISIONS.md.
116
+ const RECORD_HANDLE_SNAPSHOT_RE = /@RecordHandleSnapshot\s*\(/;
117
+
118
+ function hasRecordHandleSnapshot(serviceFilePath) {
119
+ if (!serviceFilePath || !fs.existsSync(serviceFilePath)) return false;
120
+ return RECORD_HANDLE_SNAPSHOT_RE.test(fs.readFileSync(serviceFilePath, 'utf8'));
121
+ }
122
+
107
123
  // See DECISIONS.md D-handles-ownership for the full design; the conflict/manifest/force/orphan
108
124
  // logic itself now lives in handles/_engine.mjs (D-handles-providers, G4) -- this function's job
109
125
  // is purely to compute java-spring's own render/paths and hand them to emitUnits(). `force`/
@@ -249,15 +265,26 @@ export function emitJavaSpring({ repoRoot, featureId, plan, basePackage, resourc
249
265
  if (computeDiff && migrationAction === 'update') migrationActionEntry.diff = unifiedDiff(migrationRelPath, migrationDiskContent, migrationContent);
250
266
  result.actions.push(migrationActionEntry);
251
267
 
252
- return {
253
- ...result,
254
- postEmitNotes: [
255
- 'NOT done automatically: applying specs/<id>/handles/migration.sql to any database. Review it and apply yourself.',
256
- // O4 (D-handle-lifecycle): HandleAspect.java only actually intercepts anything once a
257
- // human applies @RecordHandleSnapshot to a real service method AND the target repo has
258
- // this dependency -- never auto-added to build.gradle, same "review and apply yourself"
259
- // boundary as the migration note above.
260
- 'NOT done automatically: HandleAspect.java requires spring-boot-starter-aop on your own build.gradle classpath (Spring AOP is not enabled by any other starter). Add it yourself before applying @RecordHandleSnapshot to any service method.',
261
- ],
262
- };
268
+ const postEmitNotes = [
269
+ 'NOT done automatically: applying specs/<id>/handles/migration.sql to any database. Review it and apply yourself.',
270
+ // O4 (D-handle-lifecycle): HandleAspect.java only actually intercepts anything once a
271
+ // human applies @RecordHandleSnapshot to a real service method AND the target repo has
272
+ // this dependency -- never auto-added to build.gradle, same "review and apply yourself"
273
+ // boundary as the migration note above.
274
+ 'NOT done automatically: HandleAspect.java requires spring-boot-starter-aop on your own build.gradle classpath (Spring AOP is not enabled by any other starter). Add it yourself before applying @RecordHandleSnapshot to any service method.',
275
+ ];
276
+ // O3 follow-up (D-handle-registry-enforcement, "Continued"): per-resource, conditional on
277
+ // enforceRegistry actually being on -- see hasRecordHandleSnapshot() above.
278
+ if (enforceRegistry) {
279
+ for (const resource of plan.resources) {
280
+ if (!resource.willGenerateResolver) continue;
281
+ if (hasRecordHandleSnapshot(resource.service.file)) continue;
282
+ const relServiceFile = path.relative(repoRoot, resource.service.file);
283
+ postEmitNotes.push(
284
+ `${resource.type}: --enforce-registry is on, but no @RecordHandleSnapshot(...) was found anywhere in ${relServiceFile} -- this resource may never get its first HandleRegistry row, and every fetch()/patch() call against it will 404 until something registers it. Apply @RecordHandleSnapshot to ${resource.service.serviceType}'s own create-flow method (or call HandleService.register() by hand at least once per resource), then re-emit. See D-handle-registry-enforcement in DECISIONS.md for the full bootstrapping explanation.`,
285
+ );
286
+ }
287
+ }
288
+
289
+ return { ...result, postEmitNotes };
263
290
  }
@@ -40,6 +40,16 @@ function dottedModulePath(file, importRoot) {
40
40
  return rel.split(path.sep).join('.');
41
41
  }
42
42
 
43
+ // O3 follow-up (D-handle-registry-enforcement, "Continued"): same static, coarse, per-resource
44
+ // presence check as java-spring's own -- see that file's identical comment for the false-
45
+ // positive/false-negative bias reasoning (unchanged here).
46
+ const RECORD_SNAPSHOT_RE = /@record_snapshot\s*\(/;
47
+
48
+ function hasRecordSnapshot(routeFilePath) {
49
+ if (!routeFilePath || !fs.existsSync(routeFilePath)) return false;
50
+ return RECORD_SNAPSHOT_RE.test(fs.readFileSync(routeFilePath, 'utf8'));
51
+ }
52
+
43
53
  // See DECISIONS.md D-handles-providers. G4 follow-up: migration.sql + a real recover() lifecycle
44
54
  // (tables.py/handle_service.py/record_snapshot.py) are now generated, mirroring java-spring's own
45
55
  // O4 work -- the EXCLUDED section's original "even Java hasn't got O4" reasoning is stale, see
@@ -197,5 +207,18 @@ export function emitPythonFastApi({ repoRoot, featureId, plan, resourceFilter =
197
207
  // and apply yourself" boundary as the migration note above.
198
208
  postEmitNotes.push('NOT done automatically: applying @record_snapshot (handles/record_snapshot.py) to any of your own service functions. Codegen never touches existing business logic files.');
199
209
 
210
+ // O3 follow-up (D-handle-registry-enforcement, "Continued"): per-resource, conditional on
211
+ // enforceRegistry.
212
+ if (enforceRegistry) {
213
+ for (const resource of plan.resources) {
214
+ if (!resource.willGenerateResolver) continue;
215
+ if (hasRecordSnapshot(resource.fetchRoute.file)) continue;
216
+ const relRouteFile = path.relative(repoRoot, resource.fetchRoute.file);
217
+ postEmitNotes.push(
218
+ `${resource.type}: --enforce-registry is on, but no @record_snapshot(...) was found anywhere in ${relRouteFile} -- this resource may never get its first registry row, and every fetch/patch call against it will 404 until something registers it. Apply @record_snapshot to its own create-flow route function (or call handle_service.register() by hand at least once per resource), then re-emit. See D-handle-registry-enforcement in DECISIONS.md for the full bootstrapping explanation.`,
219
+ );
220
+ }
221
+ }
222
+
200
223
  return { ...result, postEmitNotes };
201
224
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-skeleton",
3
- "version": "1.0.0-beta.7",
3
+ "version": "1.0.0-beta.8",
4
4
  "type": "module",
5
5
  "description": "Deterministic gate layer for AI-assisted backend changes -- blocks brownfield collisions and contract/handle drift via disk-hash checks before code ships. Scaffolding codegen included (Java/Spring, Python/FastAPI, TypeScript/Express).",
6
6
  "license": "AGPL-3.0-or-later",