Ownership Manifest & Lease Headers

> Experimental: Ownership manifest APIs are still iterating. Treat the workflows below as guidance for internal teams until the UI ships broadly.

The ownership manifest tells Weldr which parts of a generated project remain fully managed, which expose safe extension points, and which surfaces are considered fully ejected. It lives at the project root as weldr.ownership.yaml and is evaluated before every regeneration.

API helpers

Automation agents can adjust scope ownership without editing YAML manually:

POST /api/ownership/update
Content-Type: application/json

{
  "chatId": "2cf1c766-6e7d-4151-9c79-5ab2a1ef0c27",
  "paths": ["lib/server/notifications/providers/slack.ts"],
  "mode": "extended",
  "description": "Extend Slack provider implementation"
}

The endpoint creates (or updates) a scope covering the provided paths and persists the manifest (both in the YAML file and the database). Valid modes are managed, extended, and ejected. Fetch the stored manifest for a chat with GET /api/ownership/ to inspect all recorded surfaces and their repo paths.

Manifest schema

version: ownership/v2
surfaces:
  model:
    mode: managed
    app_model_segments:
      - entities
      - relationships
      - workflows
  db: managed              # drizzle schema + SQL migrations
  auth: managed
  api:
    mode: extended         # generated segments preserved, custom hooks allowed
    hooks:
      - src/api/_hooks/**/*.ts
  actions:
    mode: extended
  automation:
    mode: managed
  domain_logic:
    mode: ejected
    entrypoints:
      - src/domain/**/*.ts
  ui: managed
  ui_pages:
    mode: extended
    app_model_segments:
      - pages
      - navigation
  ui_components:
    mode: extended
    eject_scopes:
      - src/app/**/Custom*.tsx
      - src/components/_custom/**/*
  ui_theme:
    mode: managed
  styling: ejected
  infra: managed
  ci: managed

Surfaces that are omitted default to managed. Structured entries allow extra metadata:

| Field | Meaning | | ------------------- | ------------------------------------------------------------ | | hooks | Glob patterns for additional user-owned files to load. | | entrypoints | Runtime import globs used when a surface is ejected. | | eject_scopes | UI/component globs that Weldr will never overwrite. | | app_model_segments| Which App Model segments the surface governs (e.g. entities, pages). |

Unknown surface keys are ignored and reported as warnings.

How it feels in the UI

  • Managed (“Generate”) — Weldr owns the surface completely. Generated files are plain TypeScript/TSX; on the next regeneration we overwrite the entire file. Use this when you never plan to touch the code by hand.
  • Extended (“Compose”) — Weldr provides @weldr … @weldr regions for the generated scaffolding and companion @custom … @custom blocks for your code. Regeneration refreshes the managed regions while preserving anything you have inside the custom slots. Choose this when you want to augment the generated logic without forking it.
  • Ejected (“Protect”) — Weldr stops writing to this surface. We still read files (for contracts or type checking) but regeneration leaves them untouched. Adopt this when you need full control or want to ship entirely bespoke code.
  • In the code panel we highlight the active mode so developers instantly see whether a file is safe to edit, shared, or fully owned.

    Switching modes safely

  • Managed → Extended
  • - Update the manifest to set mode: extended. - Regenerate once so Weldr rewrites the affected files with fresh @custom slots. - Open a file in the code panel and drop your changes between the new @custom:begin / @custom:end markers.
  • Extended → Managed
  • - Confirm the switch in the UI; the modal warns that any existing @custom code will be removed on the next regeneration. - After you regenerate, the file is rewritten without region markers. There is no safe place for manual edits, so keep everything generator-owned.
  • Extended → Ejected
  • - Promote the surface to mode: ejected when you want full ownership. - Weldr stops emitting future changes for that file. You can remove the @custom fences if you like, but keep the code handy—if you later toggle back to Compose, regeneration will rewrite the file from the managed template and you’ll need to reinsert any bespoke logic.
  • Managed → Ejected
  • - Weldr creates a one-time snapshot of the generated version, then leaves it alone. Use this when you are ready to fork the implementation entirely.

    Remember: toggling to a more automated mode (Extended → Managed) is destructive for custom edits because the merge step disappears. Toggling to a more manual mode (Managed → Extended or Ejected) requires a fresh regeneration to pick up the new structure.

    Effective state at runtime

    loadOwnershipState() resolves the manifest (or produces an all-managed default when missing). The resolved shape is attached to the IR as ir.ownership, so codegen planners and the CLI always operate with the same surface modes. The helper formatOwnershipSummary() powers DX tooling such as weldr doctor.

    File leases

    Every generated artifact now carries a header describing its surface, ownership mode, and generator:

    /** @weldr
      surface: api
      mode: extended
      region-tags: on
      generator: template
      version: ownership/v2
    **/

    In addition to the structured lease, we prepend a shorter banner (for example // @generated by Weldr.dev — api route managed…). This banner makes it obvious which files are safe to edit at a glance, while the @weldr block remains the authoritative metadata the CLI validates.

    Leases make regeneration deterministic:

  • surface ties the file back to the manifest.
  • mode records whether Weldr may overwrite, merge, or avoid the file.
  • region-tags tracks whether generated/custom segments are expected (used in later phases).
  • generator clarifies which pipeline produced the file (template, llm, or manual).
  • ensureLeaseHeader() inserts or refreshes the header without touching user code, while extractLease() parses existing files for verification.

    Extended regions

    When a surface is set to extended, Weldr emits paired regions in the generated file so that custom code survives regeneration:

    // @weldr:begin crud-order
    // generated code here
    // @weldr:end crud-order
    
    // @custom:begin order-helpers
    // developer-owned code lives here
    // @custom:end order-helpers

    During a subsequent codegen run we only replace the @weldr blocks, leaving the @custom bodies untouched. If a custom block disappears from the latest template we raise a conflict instead of silently deleting user code.

    Contracts

    contracts describe the TypeScript surface a user-owned module must expose once a surface is ejected. Each contract entry specifies a module path relative to the generated workspace and a list of exports Weldr will type-check after regeneration. Example:

    contracts:
      - id: domain.orders
        surface: domain_logic
        module: src/domain/orders.ts
        exports:
          - name: createOrder
            kind: value
            type: '(input: CreateOrderInput) => Promise<Order>'
          - name: Order
            kind: type

    After codegen, Weldr synthesises a temporary TypeScript program that imports each contract and verifies:

  • • The module exists.
  • • The named export (or default export when kind: default) is present.
  • • When a type string is supplied, the exported symbol matches the expected signature in both directions.
  • • Value exports are automatically registered as workflow actions under the ${namespace}.${export} identifier (for example, domainOrders.createOrder).
  • Failures surface as build errors (weldr doctor / weldr sync / weldr contracts check) with the contract id and export name that violated the agreement. This keeps ejected modules honest while letting developers own their implementation details.

    CLI commands

  • weldr doctor — prints the effective ownership table and highlights manifest warnings.
  • weldr leases verify — scans the local workspace, comparing file headers with the recorded manifest and current ownership policy.
  • weldr manifest verify — requires a synced workspace (weldr sync --chat ) and cross-checks every scoped file against the canonical manifest in the database (use pnpm manifest:verify to run it via package scripts).
  • weldr contracts check — validates contract adapters against developer-owned modules, reporting missing exports and type mismatches.
  • Each command accepts --dir to inspect a specific workspace.

    Defaults & safety nets

  • • Missing manifest ⇒ all surfaces managed (no warnings).
  • • Unknown manifest version ⇒ warning, but the manifest is still processed.
  • • Lease verification checks four failure modes: missing files, missing headers, manifest mismatches, and policy drifts caused by a manifest change without regeneration.
  • This layer is the foundation for the broader partial-ejection roadmap: once leases and ownership metadata are in place, we can introduce region-level merges, contract validation, and CLI workflows without worrying about silent overwrites.