/` after multi-App expansion | Run through the owning App's migration commands |
+
+The `forj make:*` commands create the common files and update their registration points together. Use the [Make Command Reference](/reference/make-commands) for exact output and registration changes, including how `--open` and `FORJ_EDITOR` open created source files. The paths above describe files in your Project, while each change type links to the guide that explains how to use it. Framework contributors can inspect the [authoritative GoForj templates](https://github.com/goforj/goforj/tree/main/templates) separately without confusing template source with application-owned files.
## Which Files Can I Edit?
@@ -193,7 +230,7 @@ Starter-kit and demo source has a different lifecycle from ordinary application
Review the [Starter Kit Guide](/getting-started/starter-kits) before customizing these paths or running `forj render`. Move durable application behavior into your own packages instead of relying on demo scaffold ownership.
-### Generated and Build Output
+### Tool-Owned and Build Output
Do not edit derived output by hand:
@@ -203,7 +240,7 @@ Do not edit derived output by hand:
- `bin/`
- frontend `dist/`
-Change the source, provider, or configuration and rebuild. [Generated Files](/reference/generated-files) lists the important generated paths and the command that owns each one.
+Change the source, provider, or configuration and rebuild. [File Ownership](/reference/generated-files) lists the important generated paths and the command that owns each one.
## Configuration at the Project Root
@@ -230,7 +267,7 @@ Selected components add focused packages under `internal/`:
| --- | --- | --- |
| Web API or Web UI | `internal/http`, `app/routes.go` | [HTTP Services](/applications/http-services) |
| Web UI starter kit | `cmd/app/frontend/` | [Starter Kit Guide](/getting-started/starter-kits) |
-| Database | `internal/database`, `migrations/` | [Database Strategy](/data/database-strategy) |
+| Database | `internal/database`, `migrations/` | [Database Connections](/data/database-strategy) |
| Cache | `internal/caches` | [Cache Patterns](/data/cache-patterns) |
| File Storage | `internal/storages` | [Storage Patterns](/data/storage-patterns) |
| Background Jobs | `internal/queues`, `internal/jobs` | [Queues](/async/queues) and [Jobs](/async/jobs) |
@@ -273,5 +310,5 @@ This refreshes generated accessors, runs Wire, updates the API index, and compil
- [JSON API Route](/scenarios/json-api-route) applies this structure to a controller, service, provider, route, and test.
- [Configuration](/getting-started/configuration) explains Project and runtime settings.
- [Apps](/core/apps) covers additional runnable apps.
-- [Generated Files](/reference/generated-files) identifies generated ownership and regeneration commands.
+- [File Ownership](/reference/generated-files) identifies ownership and regeneration commands.
- [App Lifecycle](/core/app-lifecycle) explains startup and shutdown.
diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md
index fda465b..fb6d08d 100644
--- a/docs/getting-started/quickstart.md
+++ b/docs/getting-started/quickstart.md
@@ -87,7 +87,7 @@ go test ./...
A successful run ends with `ok` lines or `[no test files]` for each generated package and no `FAIL` line.
-You now have a generated Project, a running app, a verified HTTP endpoint, and a passing test suite.
+You now have a GoForj Project, a running App, a verified HTTP endpoint, and a passing test suite.
## Next Steps
diff --git a/docs/index.md b/docs/index.md
index 72297a2..90c4e27 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -87,7 +87,7 @@ function setBinTab(id) {
}
const CAPABILITIES = [
- { title: 'HTTP services', icon: 'globe', copy: 'Thin controllers, route groups, and middleware over the web abstraction. Health, readiness, and Swagger included.', href: '/applications/http-services' },
+ { title: 'HTTP services', icon: 'globe', copy: 'Thin controllers, route groups, and middleware over the web abstraction. Health, readiness, and an OpenAPI reference included.', href: '/applications/http-services' },
{ title: 'Commands', icon: 'terminal', copy: 'First-class CLI entry points with injected dependencies, not shell scripts around your binary.', href: '/applications/commands' },
{ title: 'Queues and jobs', icon: 'rows-3', copy: 'Named, durable background work with typed payloads, retries, timeouts, and worker processes.', href: '/async/queues' },
{ title: 'Events', icon: 'radio', copy: 'Typed facts with local-first fan-out. In-process today, NATS or Kafka when you need it.', href: '/async/events' },
diff --git a/docs/libraries/atlas.md b/docs/libraries/atlas.md
index 1220aad..b8a3a95 100644
--- a/docs/libraries/atlas.md
+++ b/docs/libraries/atlas.md
@@ -8,7 +8,7 @@ repoUrl: https://github.com/goforj/atlas
# GoForj Atlas {#goforj-atlas}
-
+
Agent-native project navigation and MCP tooling for GoForj.
@@ -27,7 +27,8 @@ forj atlas:mcp
This repository contains the reusable Atlas library. The GoForj CLI exposes it
through `forj atlas:*` commands so projects do not need to install a separate
-binary.
+binary. For installation and daily use in a rendered GoForj Project, use the
+[Atlas framework guide](https://goforj.dev/developer-tools/atlas).
## What Atlas Provides {#what-atlas-provides}
@@ -49,7 +50,7 @@ When source scaffolding is needed, agents should use normal GoForj commands:
```bash
forj make:controller users
-forj marketplace make:job sync-catalog
+forj admin make:job reconcile-refunds
```
## Development {#development}
diff --git a/docs/libraries/cache.md b/docs/libraries/cache.md
index bbb5d71..e919c7d 100644
--- a/docs/libraries/cache.md
+++ b/docs/libraries/cache.md
@@ -6,7 +6,7 @@ repoUrl: https://github.com/goforj/cache
---
-
+
@@ -1399,7 +1399,11 @@ fmt.Println(c.SetString("user:42:name", "Ada", time.Minute) == nil) // true
Default integration runs cover the contract suite above. Fault/recovery restart tests run automatically when the selected integration suite includes container-backed fixtures.
-## Contributing (README updates) {#contributing-(readme-updates)}
+## Development {#development}
+
+Use `make test` for root-module tests, `make vet` for static checks, `make generate` to refresh generated documentation, and `make test-integration` for the separate integration module. Pass a driver such as `make test-integration sqlitecache` to narrow the matrix. Integration tests may require local services. Driver, docs, examples, and integration directories are independent Go modules; test each changed module from its directory.
+
+### README updates {#readme-updates}
README content is a mix of generated sections and manual sections.
@@ -1434,7 +1438,7 @@ are available.
### Watch mode {#watch-mode}
```bash
-./docs/watcher.sh
+make docs-watch
```
Notes:
@@ -1446,4 +1450,4 @@ Notes:
GoForj Apps expose named caches through generated accessors. Use those accessors in application services and keep backend selection in cache configuration.
-For the GoForj integration, see [Cache Patterns](/data/cache-patterns).
+For the App workflow, see [Cache Patterns](/data/cache-patterns).
diff --git a/docs/libraries/collection.md b/docs/libraries/collection.md
index 2f0fb43..045158a 100644
--- a/docs/libraries/collection.md
+++ b/docs/libraries/collection.md
@@ -4336,3 +4336,7 @@ collection.Dump(out3.Items())
// ]
```
+
+## Development {#development}
+
+Use `make test` for the root module, `make vet` for static checks, and `make generate` to refresh the generated README API reference. The `docs` and `examples` directories are separate Go modules and can be tested from their own directories when changed.
diff --git a/docs/libraries/console.md b/docs/libraries/console.md
index 3d7ed8a..67a4b60 100644
--- a/docs/libraries/console.md
+++ b/docs/libraries/console.md
@@ -7,7 +7,7 @@ noAutoTitle: true
---
-
+
@@ -579,7 +579,7 @@ if err := loader.Start(); err != nil {
}
// · Uploading release
loader.Fail("Registry refused upload")
-// ✖ Registry refused upload
+// ERROR Registry refused upload
```
#### Loader.Start {#loader-start}
@@ -712,7 +712,7 @@ ErrorMark returns the default console's error indicator.
```go
fmt.Println(console.ErrorMark())
-// ✖
+// ERROR
```
#### InfoMark {#infomark}
@@ -791,7 +791,7 @@ Error prints an error message through the default console.
```go
console.Error("deployment failed")
-// ✖ deployment failed
+// ERROR deployment failed
```
#### Errorf {#errorf}
@@ -800,7 +800,7 @@ Errorf prints a formatted error message through the default console.
```go
console.Errorf("deployment failed: %s", "timeout")
-// ✖ deployment failed: timeout
+// ERROR deployment failed: timeout
```
#### Fatal {#fatal}
@@ -812,7 +812,7 @@ console.SetDefault(console.New(console.Config{
Exit: func(code int) { fmt.Println("exit", code) },
}))
console.Fatal("invalid configuration")
-// ✖ invalid configuration
+// ERROR invalid configuration
// exit 1
```
@@ -825,7 +825,7 @@ console.SetDefault(console.New(console.Config{
Exit: func(code int) { fmt.Println("exit", code) },
}))
console.Fatalf("invalid port: %d", 0)
-// ✖ invalid port: 0
+// ERROR invalid port: 0
// exit 1
```
@@ -1065,7 +1065,7 @@ if err := progress.Start(); err != nil {
}
// · Publishing release
progress.Fail("Registry refused upload")
-// ✖ Registry refused upload
+// ERROR Registry refused upload
```
#### Progress.Set {#progress-set}
@@ -1329,12 +1329,12 @@ fmt.Println(errors.Is(err, console.ErrNonInteractive))
#### ASCIIMarks {#asciimarks}
-ASCIIMarks returns symbols suitable for constrained terminals and plain logs.
+ASCIIMarks returns marks suitable for constrained terminals and plain logs.
```go
marks := console.ASCIIMarks()
fmt.Println(marks.Success, marks.Warn, marks.Error)
-// + ! x
+// + ! ERROR
```
#### Config {#config}
@@ -1373,12 +1373,12 @@ fmt.Println(console.Default() != nil)
#### DefaultMarks {#defaultmarks}
-DefaultMarks returns the Unicode symbols used by a default console.
+DefaultMarks returns the semantic marks used by a default console.
```go
marks := console.DefaultMarks()
fmt.Println(marks.Success, marks.Warn, marks.Error)
-// ✔ ! ✖
+// ✔ ! ERROR
```
#### Marks {#marks}
@@ -1962,7 +1962,7 @@ console.Success("API ready\nWorker ready")
console.Warn("Configuration is incomplete")
// ! Configuration is incomplete
console.Error("Port already in use")
-// ✖ Port already in use
+// ERROR Port already in use
```
### Plain output and coordinated writers {#plain-output-and-coordinated-writers}
@@ -1980,7 +1980,7 @@ fmt.Fprintln(console.StderrWriter(), "diagnostic output")
```go
fmt.Println(console.ActionMark(), console.SuccessMark(), console.ErrorMark())
-// · ✔ ✖
+// · ✔ ERROR
fmt.Println(console.Style("release ready", console.StyleBold, console.ColorGreen))
// release ready
```
@@ -2111,7 +2111,7 @@ if err := publish.Start(); err != nil {
// · Publishing release
defer publish.Stop()
publish.Fail("Registry refused upload")
-// ✖ Registry refused upload
+// ERROR Registry refused upload
```
### Determinate progress {#determinate-progress}
@@ -2269,7 +2269,7 @@ console.List("DATABASE_URL is missing", "PORT must be between 1 and 65535")
// • DATABASE_URL is missing
// • PORT must be between 1 and 65535
console.Error("Validation failed")
-// ✖ Validation failed
+// ERROR Validation failed
```
### Recipe: machine stdout and status stderr {#recipe:-machine-stdout-and-status-stderr}
@@ -2318,18 +2318,9 @@ fmt.Print(output.String())
## Development {#development}
-```sh
-go test ./...
-go test -race ./...
-go -C docs test ./...
-go -C examples test ./...
-go generate .
-go vet ./...
-go -C docs vet ./...
-go -C examples vet ./...
-```
+`docs` and `examples` are separate Go modules so release archives contain only the library.
-The docs and examples are separate Go modules so release archives contain only the library. The README API index uses a generator-owned grouping manifest, while each local API target and code sample is generated from the declaration's GoDoc `Example:` block. Focused workflow examples come from standard Go example tests that execute and verify their inline output. Generation validates its marker pair and every example target before writing, so malformed documentation fails without partially changing the README.
+Use `make test`, `make test-race`, `make vet`, and `make generate`. The test and vet targets cover all three modules; generation rebuilds the README from its verified GoDoc examples.
## Documentation {#documentation}
@@ -2341,8 +2332,8 @@ The docs and examples are separate Go modules so release archives contain only t
Before tagging a release:
-- Run the root, docs, and examples tests and vet commands above, including the race suite.
-- Run `go generate .` and confirm the working tree has no generated or module-file diff.
+- Run `make test`, `make test-race`, and `make vet`.
+- Run `make generate` and confirm the working tree has no generated or module-file diff.
- Choose the next semantic version and review the public API and README output one final time.
- Create an annotated tag with `git tag -a vX.Y.Z -m "vX.Y.Z"`, then push it with `git push origin vX.Y.Z`.
diff --git a/docs/libraries/crypt.md b/docs/libraries/crypt.md
index 62bfbbb..da5a701 100644
--- a/docs/libraries/crypt.md
+++ b/docs/libraries/crypt.md
@@ -6,7 +6,7 @@ repoUrl: https://github.com/goforj/crypt
---
-
+
@@ -401,3 +401,7 @@ godump.Dump(err == nil, newKey != "")
// #bool true
```
+
+## Development {#development}
+
+Use `make test` for the root module, `make vet` for static checks, and `make generate` to refresh generated documentation. Run `make docs-watch` to regenerate documentation as source files change. The `examples` directory is a separate Go module and can be tested from that directory when changed.
diff --git a/docs/libraries/env.md b/docs/libraries/env.md
index 88d9538..8164000 100644
--- a/docs/libraries/env.md
+++ b/docs/libraries/env.md
@@ -6,7 +6,7 @@ repoUrl: https://github.com/goforj/env
---
-
+
@@ -1093,6 +1093,10 @@ env.Dump(
```
+## Development {#development}
+
+Use `make test` for the root module, `make vet` for static checks, and `make generate` to refresh generated documentation. The `examples` directory is a separate Go module and can be tested from that directory when changed.
+
## License {#license}
MIT
diff --git a/docs/libraries/events.md b/docs/libraries/events.md
index c28cbd7..5ebc123 100644
--- a/docs/libraries/events.md
+++ b/docs/libraries/events.md
@@ -6,7 +6,7 @@ repoUrl: https://github.com/goforj/events
---
-
+
@@ -646,18 +646,14 @@ fmt.Printf("%T\n", record.Event)
```
-## Docs Tooling {#docs-tooling}
+## Development {#development}
-The repository includes lightweight docs tooling under `docs/`.
+Use `make test` for root-module tests, `make vet` for static checks, `make generate` to refresh generated documentation, and `make test-integration` for the separate integration module. Pass a driver such as `make test-integration nats` to narrow the matrix. Integration tests may require local services. Driver, docs, examples, and integration directories are independent Go modules; test each changed module from its directory.
-Run the watcher to auto-regenerate docs on file changes:
-
-```bash
-sh docs/watcher.sh
-```
+Run `make docs-watch` to regenerate documentation as source files change.
## Using with GoForj {#using-with-goforj}
GoForj Apps expose named event buses through generated accessors. Publish through those accessors and keep driver selection in event configuration.
-For the GoForj integration, see [Events](/async/events).
+For the App workflow, see [Events](/async/events).
diff --git a/docs/libraries/execx.md b/docs/libraries/execx.md
index c295289..602ac4f 100644
--- a/docs/libraries/execx.md
+++ b/docs/libraries/execx.md
@@ -1167,14 +1167,6 @@ fmt.Println(out == dir)
`docs` and `examples` are separate Go modules, keeping their tooling and generated programs out of the library module download.
-Run the tests and rebuild the generated examples and README with:
-
-```sh
-go test ./...
-go -C docs test ./...
-go -C examples test ./...
-go -C docs run ./examplegen
-go -C docs run ./readme
-```
+Use `make test`, `make test-race`, `make vet`, and `make generate`. The test and vet targets cover all three modules; generation rebuilds the examples and README.
Licensed under the [MIT License](https://github.com/goforj/execx/blob/main/LICENSE).
diff --git a/docs/libraries/godump.md b/docs/libraries/godump.md
index cdfd46c..18ab4ae 100644
--- a/docs/libraries/godump.md
+++ b/docs/libraries/godump.md
@@ -763,3 +763,7 @@ d.Dump("hello")
// "hello" #string
```
+
+## Development {#development}
+
+Use `make test` for the root module, `make vet` for static checks, and `make modernize-check` to check for supported modernization opportunities. The `examples` directory is a separate Go module and can be tested from that directory when changed.
diff --git a/docs/libraries/httpx.md b/docs/libraries/httpx.md
index 55f803a..a03f25f 100644
--- a/docs/libraries/httpx.md
+++ b/docs/libraries/httpx.md
@@ -91,11 +91,9 @@ They are compiled and executed in CI to ensure the documentation stays accurate
httpx v1 has been tagged and is now frozen. The current module path is `github.com/goforj/httpx/v2`, and the `main` branch includes intentional breaking changes to improve API clarity and ergonomics (for example, request helpers return `(T, error)`).
-## Contributing {#contributing}
+## Development {#development}
-- Run `go run ./docs/examplegen/main.go` after updating doc examples.
-- Run `go run ./docs/readme/main.go` to refresh the API index and test count.
-- Run `go test ./...`.
+Use `make test` for the test suite, `make vet` for static checks, and `make generate` to refresh generated documentation. Keep examples and documentation accurate with behavior changes.
diff --git a/docs/libraries/index.md b/docs/libraries/index.md
index 1a4f42d..5aa8cc3 100644
--- a/docs/libraries/index.md
+++ b/docs/libraries/index.md
@@ -1,11 +1,11 @@
---
title: Libraries
-description: Standalone first-party GoForj libraries for Go services, CLIs, workers, and generated GoForj Apps.
+description: Standalone first-party GoForj libraries for Go services, CLIs, workers, and GoForj Apps.
---
# Libraries
-GoForj Libraries are first-party Go packages that can be used on their own or composed inside a generated GoForj App.
+GoForj Libraries are first-party Go packages that can be used on their own or composed inside a GoForj App.
Each library page remains useful for standalone package users. Framework guides should link here for primitive APIs, driver details, constructors, and direct package usage.
diff --git a/docs/libraries/mail.md b/docs/libraries/mail.md
index 85212f9..1f81170 100644
--- a/docs/libraries/mail.md
+++ b/docs/libraries/mail.md
@@ -6,7 +6,7 @@ repoUrl: https://github.com/goforj/mail
---
-
+
@@ -1066,15 +1066,12 @@ fmt.Println(fake.SentCount())
```
-## Docs Tooling {#docs-tooling}
+## Development {#development}
-- `go run ./docs/examplegen/main.go`
-- `go run ./docs/readme/main.go`
-- `go run ./docs/readme/testcounts/main.go`
-- `./docs/watcher.sh`
+Use `make test` for root-module tests, `make vet` for static checks, and `make generate` to refresh generated documentation. Run `make docs-watch` to regenerate documentation as source files change. The `docs`, `examples`, and `mailses` directories are separate Go modules and can be tested from their own directories when changed.
## Using with GoForj {#using-with-goforj}
GoForj Apps expose named mailers through generated accessors. Send through those accessors and keep transport selection and credentials in configuration.
-For the GoForj integration, see [Mail](/applications/mail).
+For the App workflow, see [Mail](/applications/mail).
diff --git a/docs/libraries/metrics.md b/docs/libraries/metrics.md
index efe946c..6a13d60 100644
--- a/docs/libraries/metrics.md
+++ b/docs/libraries/metrics.md
@@ -6,7 +6,7 @@ repoUrl: https://github.com/goforj/metrics
---
-
+
@@ -48,8 +48,10 @@ import (
)
func main() {
+ // Own the registry explicitly so tests and multiple applications do not share metric state.
registry := metrics.NewRegistry()
+ // Register metrics once during startup, then retain these handles for inexpensive updates.
requests := registry.MustCounter(metrics.Descriptor{
Name: "http.requests",
Help: "Total HTTP requests served.",
@@ -62,6 +64,7 @@ func main() {
http.Handle("/metrics", metrics.Handler(registry))
http.HandleFunc("/hello", func(response http.ResponseWriter, _ *http.Request) {
start := time.Now()
+ // A deferred observation records latency on every return path through the handler.
defer latency.ObserveSince(start)
requests.Inc()
@@ -74,6 +77,15 @@ func main() {
This exports `http_requests_total` and the `http_request_duration_seconds` histogram family at `/metrics`.
+After starting the program, make one request and inspect the counter:
+
+```console
+$ curl --silent http://localhost:8080/hello > /dev/null
+
+$ curl --silent http://localhost:8080/metrics | grep '^http_requests_total'
+http_requests_total 1
+```
+
## Model {#model}
Each application creates and wires one or more registries explicitly. There is no package-global registry.
@@ -195,10 +207,14 @@ Registration, updates, snapshots, and exposition are safe to use concurrently. K
- updates on the returned counter, gauge, or histogram child do not take the registry or vector lock;
- snapshots are detached and may be retained or modified without changing live metrics.
-Run the race-enabled test suite with:
+## Development {#development}
-```sh
-GOCACHE=/tmp/gocache GOMODCACHE=/tmp/gomodcache go test -race ./...
+Run the normal validation targets with:
+
+```bash
+make test
+make test-race
+make vet
```
## Upgrading {#upgrading}
@@ -209,4 +225,4 @@ The quality-pass contract is stricter than v0.1.0. See [MIGRATING.md](https://gi
GoForj Apps expose metrics through the observability and HTTP runtime. Keep registration close to the behavior being measured and configure scrape exposure through the App runtime.
-For the GoForj integration, see [Metrics](/operations/metrics).
+For the App workflow, see [Metrics](/operations/metrics).
diff --git a/docs/libraries/queue.md b/docs/libraries/queue.md
index 22ca702..93db585 100644
--- a/docs/libraries/queue.md
+++ b/docs/libraries/queue.md
@@ -7,7 +7,7 @@ noAutoTitle: true
---
-
+
@@ -2474,34 +2474,14 @@ fmt.Println(q != nil)
```
-## Contributing {#contributing}
+## Development {#development}
-### Testing {#testing}
+Use `make test` for root-module tests, `make vet` for static checks, and `make generate` to refresh generated documentation. `make test-integration` runs the separate integration module; pass a backend such as `make test-integration sqlite` to narrow the matrix. Integration tests may need local services.
-Unit tests (root module):
-
-```bash
-go test ./...
-```
-
-Integration tests (separate `integration` module):
-
-```bash
-go test -tags=integration ./integration/...
-```
-
-Select specific backends with `INTEGRATION_BACKEND` (comma-separated), for example:
-
-```bash
-INTEGRATION_BACKEND=sqlite go test -tags=integration ./integration/...
-INTEGRATION_BACKEND=redis,rabbitmq go test -tags=integration ./integration/... -count=1
-INTEGRATION_BACKEND=all go test -tags=integration ./integration/... -count=1
-```
-
-Matrix status and backend integration notes are tracked in `docs/integration-scenarios.md`.
+Driver, docs, examples, and integration directories are independent Go modules; test each changed module from its directory. Matrix status and backend notes are tracked in `docs/integration-scenarios.md`.
## Using with GoForj {#using-with-goforj}
GoForj Apps expose named queues through generated accessors. Dispatch jobs through those accessors and keep backend selection in queue configuration.
-For the GoForj integration, see [Queues](/async/queues).
+For the App workflow, see [Queues](/async/queues).
diff --git a/docs/libraries/scheduler.md b/docs/libraries/scheduler.md
index df6a2cc..183575a 100644
--- a/docs/libraries/scheduler.md
+++ b/docs/libraries/scheduler.md
@@ -6,7 +6,7 @@ repoUrl: https://github.com/goforj/scheduler
---
-
+
@@ -65,24 +65,24 @@ go get github.com/redis/go-redis/v9
### Basic {#basic}
```go
-s := scheduler.New()
-defer s.Stop()
+schedule := scheduler.New()
+defer schedule.Stop()
-s.EveryMinute().Name("cleanup").Do(func(context.Context) error { return runCleanup() }) // run in-process cleanup every minute
-s.DailyAt("09:00").Weekdays().Name("reports:morning").Do(func(context.Context) error { return sendMorningReport() }) // weekdays at 09:00
-s.Cron("0 0 * * *").Command("reports:purge", "--force") // run app subcommand nightly
-s.Cron("*/15 * * * *").Exec("/usr/bin/env", "echo", "heartbeat") // run external executable every 15 minutes
-s.EveryFiveMinutes().WithoutOverlapping().Name("sync:inventory").Do(func(context.Context) error { return syncInventory() }) // prevent overlapping runs
-s.Cron("0 * * * *").When(func() bool { return isPrimaryNode() }).Name("rebalance").Do(func(context.Context) error { return rebalance() }) // run only when condition passes
+schedule.EveryMinute().Name("cleanup").Do(func(context.Context) error { return runCleanup() }) // run in-process cleanup every minute
+schedule.DailyAt("09:00").Weekdays().Name("reports:morning").Do(func(context.Context) error { return sendMorningReport() }) // weekdays at 09:00
+schedule.Cron("0 0 * * *").Command("reports:purge", "--force") // run app subcommand nightly
+schedule.Cron("*/15 * * * *").Exec("/usr/bin/env", "echo", "heartbeat") // run external executable every 15 minutes
+schedule.EveryFiveMinutes().WithoutOverlapping().Name("sync:inventory").Do(func(context.Context) error { return syncInventory() }) // prevent overlapping runs
+schedule.Cron("0 * * * *").When(func() bool { return isPrimaryNode() }).Name("rebalance").Do(func(context.Context) error { return rebalance() }) // run only when condition passes
```
### Advanced (kitchen sink) {#advanced-(kitchen-sink)}
```go
-s := scheduler.New()
-defer s.Stop()
+schedule := scheduler.New()
+defer schedule.Stop()
-s.
+schedule.
Name("reports:generate").
Timezone("America/New_York").
Weekdays().
@@ -94,7 +94,7 @@ s.
DailyAt("10:30").
Do(func(context.Context) error { return generateReports() })
-s.
+schedule.
Name("reconcile:daily").
RunInBackground().
Cron("0 3 * * *").
@@ -108,33 +108,34 @@ package main
import (
"context"
+
"github.com/goforj/scheduler/v2"
)
func main() {
- s := scheduler.New()
- defer s.Stop()
+ schedule := scheduler.New()
+ defer schedule.Stop()
- s.EveryMinute().Name("cleanup").Do(func(context.Context) error { return nil }) // run cleanup every minute
- s.DailyAt("10:30").Weekdays().Name("reports:generate").Do(func(context.Context) error { return nil }) // run reports on weekdays at 10:30
- s.Cron("0 0 * * *").Command("reports:purge", "--force") // run app subcommand nightly at midnight
+ schedule.EveryMinute().Name("cleanup").Do(func(context.Context) error { return nil }) // run cleanup every minute
+ schedule.DailyAt("10:30").Weekdays().Name("reports:generate").Do(func(context.Context) error { return nil }) // run reports on weekdays at 10:30
+ schedule.Cron("0 0 * * *").Command("reports:purge", "--force") // run app subcommand nightly at midnight
- s.PrintJobsList()
+ schedule.PrintJobsList()
}
```
Example output:
```
-+------------------------------------------------------------------------------------------------------------------------+
-| Scheduler Jobs › (3) |
-+------------------+----------+----------------+-----------------------+----------------------+--------------------------+
-| Name | Type | Schedule | Handler | Next Run | Tags |
-+------------------+----------+----------------+-----------------------+----------------------+--------------------------+
-| cleanup | function | every 1m | main.main (anon func) | in 1m Mar 3 2:16AM | env=local |
-| reports:generate | function | cron 30 10 * * * | main.main (anon func) | in 8h Mar 3 10:30AM | env=local |
-| reports:purge | command | cron 0 0 * * * | - | in 21h Mar 4 12:00AM | env=local, args="--force" |
-+------------------+----------+----------------+-----------------------+----------------------+--------------------------+
++---------------------------------------------------------------------------------------------------------------------------+
+| Scheduler Jobs › (3) |
++------------------+----------+------------------+-----------------------+----------------------+---------------------------+
+| Name | Type | Schedule | Handler | Next Run | Tags |
++------------------+----------+------------------+-----------------------+----------------------+---------------------------+
+| cleanup | function | every 1m | main.main (anon func) | in 1m Mar 3 2:16AM | env=local |
+| reports:generate | function | cron 30 10 * * * | main.main (anon func) | in 8h Mar 3 10:30AM | env=local |
+| reports:purge | command | cron 0 0 * * * | - | in 21h Mar 4 12:00AM | env=local, args="--force" |
++------------------+----------+------------------+-----------------------+----------------------+---------------------------+
```
## Runnable examples {#runnable-examples}
@@ -1112,8 +1113,19 @@ scheduler.New().Name("cleanup").Cron("0 0 * * *").Do(func(context.Context) error
```
+## Development {#development}
+
+Use the repository targets so the API examples and README stay synchronized:
+
+```bash
+make test
+make test-race
+make vet
+make generate
+```
+
## Using with GoForj {#using-with-goforj}
GoForj Apps register schedules in the scheduler runtime and inject the jobs they run. Keep recurring business work in jobs instead of the schedule registry.
-For the GoForj integration, see [Scheduler](/async/scheduler).
+For the App workflow, see [Scheduler](/async/scheduler).
diff --git a/docs/libraries/storage.md b/docs/libraries/storage.md
index 053be1f..f3bc02c 100644
--- a/docs/libraries/storage.md
+++ b/docs/libraries/storage.md
@@ -7,7 +7,7 @@ noAutoTitle: true
---
-
+
@@ -1375,36 +1375,12 @@ Current fixture types in the centralized matrix:
- emulator: `gcs`
- embedded/local fixtures: `local`, `ftp`, `rclone_local`
-Common contributor commands:
+## Development {#development}
-```bash
-go test ./...
-```
-
-```bash
-cd integration
-go test -tags=integration ./all -count=1
-```
-
-Run a single integration backend:
-
-```bash
-cd integration
-INTEGRATION_DRIVER=gcs go test -tags=integration ./all -count=1
-```
-
-Make targets:
-
-```bash
-make test
-make examples-test
-make coverage
-make integration
-make integration-driver gcs
-```
+Use `make test` for root-module tests, `make test-examples` for the examples module, and `make test-coverage` for the Codecov report. `make test-integration` runs the centralized matrix; pass a driver such as `make test-integration gcs` to select one backend. Integration may require Docker. `make bench` and `make bench-render` retain the benchmark workflow, while the `modules-check`, `release-tag`, `release-plan`, and `release-publish` targets support publication.
## Using with GoForj {#using-with-goforj}
GoForj Apps expose named disks through generated accessors. Use those accessors in application services and keep backend selection in storage configuration.
-For the GoForj integration, see [Storage Patterns](/data/storage-patterns).
+For the App workflow, see [Storage Patterns](/data/storage-patterns).
diff --git a/docs/libraries/strings.md b/docs/libraries/strings.md
index 98f960f..60ac47c 100644
--- a/docs/libraries/strings.md
+++ b/docs/libraries/strings.md
@@ -1293,14 +1293,6 @@ println(v)
`docs` and `examples` are separate Go modules, keeping their tooling and generated programs out of the library module download.
-Run the tests and rebuild the generated examples and README with:
-
-```sh
-go test ./...
-go -C docs test ./...
-go -C examples test ./...
-go -C docs run ./examplegen
-go -C docs run ./readme
-```
+Use `make test`, `make test-race`, `make vet`, and `make generate`. The test and vet targets cover all three modules; generation rebuilds the examples and README.
Licensed under the [MIT License](https://github.com/goforj/str/blob/main/LICENSE).
diff --git a/docs/libraries/web.md b/docs/libraries/web.md
index 241043c..2a32ade 100644
--- a/docs/libraries/web.md
+++ b/docs/libraries/web.md
@@ -90,7 +90,7 @@ The quick start owns the `http.Server` directly to keep the first example small.
| Start with | Use it when |
| --- | --- |
| `echoweb.New()` and `router.GET(...)` | Routes are registered directly and your application owns the `http.Server`. |
-| `web.NewRouteGroup(...)` and `web.RegisterRoutes(...)` | Routes should be reusable declarations for reporting, indexing, or App composition. |
+| `web.NewRouteGroup(...)` and `web.RegisterRoutes(...)` | Routes should be reusable declarations for reporting, indexing, or framework-managed App composition. |
| `echoweb.NewServer(...)` | The adapter should register route groups and own graceful HTTP shutdown. |
| `echoweb.Wrap(engine)` | An existing Echo engine needs to expose the app-facing `web.Router` contract. |
@@ -298,7 +298,7 @@ Fiber is omitted because its `fasthttp` engine is not directly comparable in thi
Regenerate the measurement and image with:
```sh
-make benchmark-svg
+make bench-svg
```
@@ -1804,8 +1804,18 @@ fmt.Println(ctx.Param("id"), ctx.Query("expand"))
```
+## Development {#development}
+
+Use the repository targets to validate every module and refresh executable documentation:
+
+```bash
+make test
+make vet
+make generate
+```
+
## Using with GoForj {#using-with-goforj}
GoForj Apps register web routes and controllers through the HTTP runtime. Keep server wiring in framework providers and inject application services into controllers.
-For the GoForj integration, see [HTTP Services](/applications/http-services).
+For the App workflow, see [HTTP Services](/applications/http-services).
diff --git a/docs/libraries/wire.md b/docs/libraries/wire.md
index a5a2730..68b8d3c 100644
--- a/docs/libraries/wire.md
+++ b/docs/libraries/wire.md
@@ -150,13 +150,17 @@ runtime dependency on Wire.
## Running generation manually {#running-generation-manually}
-Run the default command (alias for `wire gen`) or specify packages:
+Use `make generate` to run `wire gen` across the repository. The CLI remains useful for targeted generation:
```sh
wire
wire gen ./...
```
+## Development {#development}
+
+Use `make test` for the test suite and `make vet` for static checks.
+
## Watching for changes {#watching-for-changes}
Wire includes a native watcher that re-runs generation on Go file changes:
diff --git a/docs/operations/deployment-basics.md b/docs/operations/deployment-basics.md
index 4ad7f36..de6f1f6 100644
--- a/docs/operations/deployment-basics.md
+++ b/docs/operations/deployment-basics.md
@@ -20,6 +20,8 @@ flowchart LR
This page owns that path. The linked operations pages cover process topology, probes, metrics, backups, and security policy in more depth.
+This page uses `forj` while preparing source and `./bin/` after an artifact has been built. Production supervisors and release checks should execute the exact binary being deployed, not source-aware development commands.
+
## Before You Start
Decide which Apps belong in the release and where they will run. Provision the production database, queue, cache, storage, and other external services selected by those Apps. The deployment platform must also own DNS, TLS termination, network policy, process supervision, and secret delivery.
@@ -35,7 +37,7 @@ forj build &&
test -x ./bin/app
```
-Expected result: `bin/app` exists and is executable. The build refreshes generated Project files, runs Wire, prepares the API index, and compiles the default App.
+Expected result: `bin/app` exists and is executable. The build refreshes Framework-managed Project files, runs Wire, prepares the API index, and compiles the default App.
### Apps with Web UI
@@ -51,17 +53,31 @@ Expected result: `cmd/app/frontend/dist` contains current frontend output and `b
### Additional Apps
-Build each independently deployable App:
+Build each independently deployable App. If it has frontend source, build that App's frontend first using its path under `cmd//frontend/`:
```bash
-forj admin build &&
+npm --prefix cmd/admin/frontend ci &&
+ npm --prefix cmd/admin/frontend run build &&
+ forj admin build &&
test -x ./bin/admin
```
-Expected result: `bin/admin` contains the staff-facing App selected by the command prefix. Repeat the build for every App included in the release.
+Expected result: `cmd/admin/frontend/dist` contains the current staff frontend and `bin/admin` contains the staff-facing App selected by the command prefix. Omit the npm steps when that App has no frontend source. Repeat the applicable frontend build, App build, and artifact check for every App included in the release.
Keep the artifact immutable after it has been tested. If a release is transferred to another host or registry, verify its checksum or image digest before activation.
+### Hand Off the Artifact
+
+The build system should hand the deployment system one identified, immutable release. Record at least:
+
+- the binary or image digest;
+- every App binary included in the release;
+- the target operating system and architecture;
+- the source revision and build time; and
+- any non-secret defaults or overrides compiled with `forj build`.
+
+Frontend output is already embedded in an App binary after the frontend build and `forj build`; do not deploy an unrelated `dist` directory beside it. Promote the same tested bytes between environments. If configuration or frontend assets require a rebuild, assign the result a new release identity and repeat artifact checks.
+
## Keep Configuration Outside the Artifact
Supply production configuration through the process environment or the secret and configuration mechanism provided by the deployment platform.
@@ -81,6 +97,8 @@ APP_DIAG_TOKEN=
The rendered App determines the rest. Configure its selected database, queue, cache, storage, event, mail, and observability drivers with production values. Do not allow a missing production dependency to silently fall back to a process-local driver.
+`forj build --env-defaults` and `--env-overrides` are explicit exceptions: they pin non-secret values into the binary. Defaults remain replaceable by deployment configuration; overrides do not. Use defaults only for artifact-level fallbacks and overrides only when every deployment of that artifact must use the same value. A rotated credential, environment endpoint, port, replica-specific identity, or retention setting belongs to the deployment system instead. See [Compiled Environment Values](/reference/configuration#compiled-environment-values) for exact precedence.
+
Bind to `127.0.0.1` when a reverse proxy on the same host owns public traffic. Bind to `0.0.0.0` only when a container network, firewall, or host network policy controls access.
If the App intentionally uses SQLite, local storage, uploads, or another writable filesystem path, place that data outside the versioned release directory and configure an absolute path. Replacing an immutable release must not replace or orphan durable application data.
@@ -110,6 +128,16 @@ Changing the command does not change application behavior or make in-memory driv
Run one scheduler process unless the schedules use deliberate cross-process locking. See [Runtime Processes](/operations/runtime-processes) before introducing a split topology.
+A concrete split deployment might use the same immutable artifact in these supervised process groups:
+
+| Process group | Replicas | Traffic or work handoff | Scaling signal |
+| --- | ---: | --- | --- |
+| `./bin/app api` | Two or more | The platform sends HTTP traffic only to ready instances. | Request load, latency, and resource use. |
+| `./bin/app worker --queue emails` | One or more | A shared queue backend hands jobs to workers. | Queue depth, job age, failures, and resource use. |
+| `./bin/app scheduler` | One | The supervisor maintains singleton ownership unless schedules use a shared lock. | Availability and due-run outcomes, not HTTP load. |
+
+This is a topology example, not a required replica count. Each group receives its own environment, resource limits, probe configuration, and restart policy. If an App has another binary such as `bin/admin`, model its HTTP, worker, and scheduler roles independently rather than assuming the default App process owns them.
+
## Give the Process to a Supervisor
The deployment platform should:
@@ -125,7 +153,21 @@ The deployment platform should:
This contract applies equally to systemd, a container runtime, Kubernetes, Nomad, or another supervisor. The exact service unit or workload manifest belongs to that platform.
-Set the supervisor's stop grace period above the longest effective App shutdown path, with additional margin for the supervisor itself. In combined mode, runtime shutdown happens concurrently before the outer App lifecycle finishes. Test shutdown with real in-flight jobs instead of relying only on arithmetic.
+### Budget Timeouts as a System
+
+Timeouts protect different boundaries and should be planned together:
+
+| Boundary | Example control | What it bounds |
+| --- | --- | --- |
+| One unit of work | A job timeout or `SCHEDULER_COMMAND_TIMEOUT` | The handler or scheduled command execution. |
+| Runtime cleanup | `QUEUE_SHUTDOWN_TIMEOUT` | Queue drain and backend cleanup inside App shutdown. |
+| App shutdown | `APP_SHUTDOWN_TIMEOUT` | The HTTP or scheduler graceful-stop path and outer App lifecycle. |
+| Readiness request | Probe or `health --timeout-ms` | One operator or platform request, including sequential resource checks. |
+| Process supervision | Platform stop grace period | The complete interval before the platform may force termination. |
+
+The App resolves this policy once at startup. A queue or scheduler subprocess value larger than `APP_SHUTDOWN_TIMEOUT` is capped to the App budget and emits one structured warning with the configured and effective values. Run `./bin/app about` to inspect the effective Runtime settings before changing supervisor limits.
+
+Do not add every configured duration and assume that sum is the required supervisor value. Some shutdown work is concurrent, some limits are nested, and a handler that ignores cancellation can outlive its intended budget. Set the supervisor's stop grace period above the longest observed graceful-stop path with margin for traffic removal and supervisor overhead. In combined mode, runtime shutdown happens concurrently before the outer App lifecycle finishes. Test `SIGTERM` with real in-flight requests, jobs, and scheduled commands; make interrupted work safe to retry.
The long-running command must be the deployed binary:
@@ -149,8 +191,30 @@ Expected result: the migration command exits successfully before processes from
Run the command once per migration-owning App, not once per HTTP replica. During a rolling deployment, prefer additive schema changes that work with both the old and new binaries.
+For an additional migration-owning App, run that App's staged binary independently:
+
+```bash
+/srv/example/releases/2026-07-28/bin/admin migrate
+```
+
+Expected result: only the `admin` App's migration streams run. Repeat this once for each migration-owning App in the release; do not infer that the default App command migrated additional Apps.
+
Use stable paths for backup output rather than writing backup sets inside a versioned release directory. [Backup and Restore](/operations/backups) covers discovery, verification, retention, and restore safeguards.
+## Hand Off Observability and Retention
+
+The App emits signals; the deployment platform and operators own their transport, access, and retention. Before activation, assign each signal to an operational destination:
+
+| Signal or data | App/process responsibility | Deployment responsibility |
+| --- | --- | --- |
+| Logs | Write structured runtime output to standard output and standard error. | Collect, index, redact, retain, and alert on it. |
+| Metrics | Expose the endpoint appropriate to combined or split topology. | Scrape it with bounded labels, retain time series, and define alerts. |
+| Health and readiness | Report process liveness and required dependency state. | Route probes correctly and remove unready HTTP instances from traffic. |
+| Inspects and Lighthouse | Capture and present bounded recent execution detail when enabled. | Restrict operator access and choose capture, sampling, and recent-window limits. |
+| Backups and durable data | Use the configured stable paths and backends. | Schedule backups, apply retention, verify restore, and keep data outside release directories. |
+
+Preserve the app name, runtime role, release identity, and instance identity in the surrounding platform metadata so an alert can be traced to the exact process and artifact. Set `APP_VERSION` and `APP_REVISION` while building framework-managed metrics discovery, and apply equivalent bounded labels in an external production scraper. Lighthouse's recent Inspect window is not a substitute for retained logs, metrics, or backups.
+
## Activate the Release
A useful filesystem layout separates immutable releases from mutable state:
@@ -246,12 +310,14 @@ Queue payloads are another compatibility boundary. A previous worker binary must
- Build every App for the deployment target.
- Build frontend assets before `forj build` when the App has Web UI.
- Store production configuration and secrets outside the artifact.
+- Record any non-secret defaults or overrides intentionally compiled into it.
- Use production drivers for state shared across processes or hosts.
- Keep writable data and backup sets outside immutable release directories.
- Run the staged release's migrations once before it receives traffic.
- Supervise each required process with the deployed binary.
- Keep the scheduler singleton unless locking makes overlap safe.
- Set and test graceful shutdown budgets.
+- Assign logs, metrics, Inspects, and backups explicit access and retention owners.
- Verify liveness, readiness, metrics, and one application workflow.
- Keep the previous artifact available for binary rollback.
- Treat database migrations and queued payloads as separate rollback contracts.
diff --git a/docs/operations/http-server.md b/docs/operations/http-server.md
index ec821e2..3cb8d1d 100644
--- a/docs/operations/http-server.md
+++ b/docs/operations/http-server.md
@@ -77,6 +77,20 @@ For a staff operations App named `admin`, use its binary:
Expected result: a complete, human-readable table of the staff-facing methods, paths, and handlers registered by `admin`. The command constructs the App route surface but does not start the HTTP listener.
+A small route table looks like this (terminal output uses color when supported):
+
+```text
++-------------------+---------+-----------------------+------------+
+| API Routes › (1)
++-------------------+---------+-----------------------+------------+
+| Path | Methods | Handler | Middleware |
++-------------------+---------+-----------------------+------------+
+| /api/v1/users/:id | GET | users.Controller.Show | |
++-------------------+---------+-----------------------+------------+
+```
+
+Long middleware names may be replaced by short codes with a `Middleware Legend` above the route title. The columns remain `Path`, `Methods`, `Handler`, and `Middleware`.
+
## Health, Readiness, and Verification
Use liveness to answer whether the process is responding and readiness to decide whether it should receive traffic:
diff --git a/docs/operations/index.md b/docs/operations/index.md
index 4da6218..564abe2 100644
--- a/docs/operations/index.md
+++ b/docs/operations/index.md
@@ -19,6 +19,7 @@ Use these guides when you need to run, split, observe, deploy, or recover an App
| Configure probes | [Health and Readiness](/operations/health-readiness) |
| Investigate runtime behavior | [Logging](/operations/logging), [Metrics](/operations/metrics), and [Inspects](/operations/inspects) |
| Use the operator interface | [Lighthouse](/operations/lighthouse) |
+| Compare HTTP and infrastructure performance safely | [Performance Benchmarks](/operations/performance-benchmarks) |
| Protect and recover durable state | [Backup and Restore](/operations/backups) |
| Build, roll out, verify, and roll back a release | [Deploy an App](/operations/deployment-basics) |
diff --git a/docs/operations/lighthouse.md b/docs/operations/lighthouse.md
index a9e4004..3d0c6b2 100644
--- a/docs/operations/lighthouse.md
+++ b/docs/operations/lighthouse.md
@@ -9,6 +9,12 @@ Lighthouse is GoForj's UI for inspecting running applications during development
It brings inspects, logs, routes, schedules, queue state, cache, storage, and metrics-backed views into one operator workspace.
+
+
+Instead of reconstructing a failure from several terminals, select the app and Runtime that produced it, follow its recent execution timeline, and move directly to the registered route, queue, schedule, cache, or storage resource involved. The workspace follows the components compiled into each App, so a focused worker and a full web app do not pretend to expose the same controls.
+
+Use Lighthouse when you need the fast, connected view. Keep logs, metrics, health checks, and Inspects independently useful so production diagnosis never depends on one UI.
+
## Role
Lighthouse should present already-useful operational data.
@@ -90,6 +96,12 @@ Queue actions depend on the selected queue backend. The queue contract defines a
Treat operator actions as production changes. Before retrying or deleting queue work, identify the app, process, queue, job, and driver capability; use application logs, metrics, and inspects to establish impact. A control that returns unsupported is a driver capability limit, not an empty queue.
+## Browser Benchmarks
+
+Lighthouse includes a browser workflow for controlled HTTP, cache, queue, storage, and database comparisons. It discovers only the suites and configured resource instances available to the selected App, then reports throughput, latency percentiles, errors, driver details, and the system baseline together.
+
+Benchmarks create real load and can leave data behind after interruption or backend failure. Read [Performance Benchmarks](/operations/performance-benchmarks) before choosing a target, changing concurrency, or using results for a capacity decision.
+
## Operational Workflow
Use Lighthouse after the underlying signal has identified a problem:
@@ -118,10 +130,12 @@ This order keeps Lighthouse a useful operator view without turning it into the o
- Every replica has an unambiguous app, process role, and instance identity.
- Connection and authentication warnings are collected with application logs.
- A Lighthouse outage has a documented fallback using direct health, logs, metrics, and app commands.
+- Browser benchmark access is limited to operators, and production runs require a reviewed target and load budget.
## Next Steps
- [Inspects](/operations/inspects)
- [Metrics](/operations/metrics)
+- [Performance Benchmarks](/operations/performance-benchmarks)
- [Environment Reference](/reference/env-vars#lighthouse-and-inspects)
-- [Generated Extension Points](/core/code-generation#choose-a-safe-extension-point)
+- [App Extension Points](/core/code-generation#choose-a-safe-extension-point)
diff --git a/docs/operations/logging.md b/docs/operations/logging.md
index 5350812..9869d21 100644
--- a/docs/operations/logging.md
+++ b/docs/operations/logging.md
@@ -81,6 +81,22 @@ Each request event retains named fields for URI, method, status, latency, and cl
Console output uses a compact value-oriented line with status-aware color. JSON output and registered log sinks retain the structured field names, so machine processing does not depend on console formatting.
+An App logger can route one structured entry to several destinations. `AddSink` preserves the simple synchronous callback API. Use `AddSinkWithOptions` when a destination needs error isolation or a bounded asynchronous queue:
+
+
+```go
+if err := appLogger.AddSinkWithOptions(auditSink, logger.SinkOptions{
+ Name: "audit",
+ Async: true,
+ QueueCapacity: 256,
+ Overflow: logger.SinkOverflowDropOldest,
+}); err != nil {
+ return err
+}
+```
+
+Registrations append; they do not replace process output or an earlier sink. Synchronous sinks run in the logging path, so keep them fast. Asynchronous sinks preserve accepted entries in FIFO order and make overload behavior explicit with `block`, `drop-newest`, or `drop-oldest`. Flush managed sinks from an App shutdown hook with `appLogger.FlushSinks(ctx)` so accepted entries get the remaining shutdown budget.
+
Disable access logs for a runtime where request volume would hide higher-signal events, then rely on metrics and inspects for the intended visibility.
Keep access logs enabled during a new deployment until the request path, status, and latency are understood. If you disable them for steady-state volume, retain an explicit route-level metric and a safe Inspect sampling policy; otherwise a 5xx increase has no request-level path back to an operator.
@@ -97,7 +113,16 @@ APP_LOG_TIME
Use structured fields that answer an operational question: App identity, Runtime source, route pattern, queue or schedule name, status, and latency. The request context can carry the `trace_id` correlation field used by Inspects, but the product surface is still called an Inspect.
-Never log authorization headers, cookies, credentials, raw queue payloads, or unredacted request bodies by default. Local HTTP error-response capture is intentionally local-environment behavior; do not rely on it as a production payload-dump mechanism.
+Never log authorization headers, cookies, credentials, raw queue payloads, or unredacted request bodies by default. Before deduplication, process output, or sink delivery, the App logger redacts common secret-bearing field names and high-confidence message forms such as bearer tokens and `password=...` assignments.
+
+Extend that mandatory policy for application-specific data:
+
+```dotenv
+APP_LOG_REDACT_KEYS=session_id,customer_reference
+APP_LOG_REDACT_MESSAGE_PATTERNS=["client_secret=[^ ]+","session=[^ ]+"]
+```
+
+The first value is a comma-separated list of additional case-insensitive field names. The second is a JSON array of regular expressions; invalid JSON or expressions fail during logger construction instead of silently weakening the policy. These controls supplement the framework defaults rather than disabling them. Redaction is a safety net, not permission to log whole payloads. Local HTTP error-response capture remains intentionally local-environment behavior.
## Failure Modes
diff --git a/docs/operations/metrics.md b/docs/operations/metrics.md
index 7a14e7e..205d637 100644
--- a/docs/operations/metrics.md
+++ b/docs/operations/metrics.md
@@ -113,6 +113,25 @@ environment=local
This label set identifies a background staff-operations job from `admin`. Use `source` for logical runtime attribution and `process` for scrape topology.
+## Deployment Markers
+
+Set release identity in the Project-owned environment used to generate the deployment artifact:
+
+```dotenv
+APP_VERSION=v1.8.0
+APP_REVISION=8d5c20f
+```
+
+Then build the artifact:
+
+```bash
+forj build
+```
+
+When present, `APP_VERSION` becomes the bounded `release` target label and `APP_REVISION` becomes `revision`. Blank values are omitted. Keep both values low-cardinality: use one release version and one immutable commit or artifact revision, not a request ID or build timestamp. This lets a dashboard annotation or deployment comparison separate a real regression from ordinary traffic movement.
+
+Generation intentionally reads `.env.example` and `.env`, not unrelated ambient shell variables, so the discovery file and the artifact are derived from one reviewable Project snapshot. These labels are written into framework-managed local discovery files during generation. In an externally managed production scraper, apply the same release and revision labels in that platform's service-discovery configuration.
+
Avoid user IDs, emails, raw URLs, raw SQL, cache keys, filenames, request IDs, and arbitrary error strings.
## Proving Path
diff --git a/docs/operations/performance-benchmarks.md b/docs/operations/performance-benchmarks.md
new file mode 100644
index 0000000..5c5b48d
--- /dev/null
+++ b/docs/operations/performance-benchmarks.md
@@ -0,0 +1,98 @@
+---
+title: Performance Benchmarks
+description: Run and interpret Lighthouse browser benchmarks for HTTP, cache, queues, storage, and databases.
+---
+
+# Performance Benchmarks
+
+Lighthouse can send controlled workload through a connected App and compare HTTP or configured infrastructure resources from the browser.
+
+Use this page to establish a reproducible baseline, compare an intentional driver or configuration change, or find the point where higher concurrency stops helping. These benchmarks measure the selected suite under the selected conditions; they do not predict whole-application capacity or define a service-level objective.
+
+## What Lighthouse Can Measure
+
+The Benchmarks view asks a connected benchmark-capable agent for its catalog. The available rows follow the components compiled into that App and the resource instances its Runtime discovers.
+
+| Suite | Target | Work performed |
+| --- | --- | --- |
+| HTTP | Configured benchmark URL and path | Repeated `GET` requests over the configured duration. |
+| Cache | One configured cache instance | Benchmark-owned keys and payloads against that Driver. |
+| Queue | One configured queue instance | Benchmark jobs dispatched and drained through the selected queue. |
+| Storage | One configured storage disk | Benchmark-owned paths and payloads against that Driver. |
+| Database | One configured connection | Operations against a benchmark table created when needed. |
+
+Lighthouse does not show a cache, storage, or database suite merely because the UI knows its name. The App must compile the component and expose a usable instance. Queue is part of the benchmark runner, but a meaningful queue run also needs workers consuming the selected benchmark queue.
+
+## Before You Run
+
+Treat a benchmark as an operator change, not a read-only diagnostic.
+
+1. Prefer an isolated or representative environment with the same topology you want to compare.
+2. Select the exact App, Runtime agent, resource instance, and Driver.
+3. Confirm the HTTP URL and path or queue name before sending work.
+4. Record the release, agent instance, duration, concurrency, payload size, sweep limit, and surrounding load.
+5. Watch normal CPU, memory, network, connection-pool, broker, database, storage, error, and saturation signals during the run.
+
+::: warning Active workload
+The HTTP suite repeatedly sends `GET` requests. Do not point it at a route that mutates data or triggers expensive side effects. Resource suites use run-scoped benchmark keys, paths, jobs, and records and attempt cleanup, but a crash, cancellation, backend error, or unavailable cleanup operation can leave data or queued work behind. The database suite can create its benchmark table.
+:::
+
+## Run a Baseline in Lighthouse
+
+Open **Benchmarks** in Lighthouse, then:
+
+1. Confirm the selected agent. Lighthouse prefers a connected jobs Runtime when one is available because the generated benchmark runner lives with App job infrastructure.
+2. Select one target first. Each configured cache, queue, storage, or database instance appears as its own target.
+3. Keep the initial duration, concurrency, and payload settings unchanged so the first result is a baseline rather than a tuning experiment.
+4. Select **Run Baseline** and keep the page attached until the target finishes.
+5. Save the target identity, system baseline, result details, and any errors with your change record.
+
+Expected result: the page reports the suite and Driver, configured duration and concurrency, actual elapsed time, operation count, operations per second, errors, and p50, p95, and p99 latency. Suite-specific detail and system information appear with the report.
+
+If the target does not appear, confirm that the component and resource instance belong to the selected App. If a queue run stalls, confirm workers are consuming the selected queue before increasing the drain timeout or concurrency.
+
+## Repeat the Baseline from the Artifact
+
+The built App exposes the same generated runner for repeatable release checks. For an HTTP-only baseline:
+
+```bash
+./bin/app benchmark:run \
+ --suites http \
+ --duration-ms 15000 \
+ --concurrency 8 \
+ --payload-size 512
+```
+
+Expected result: the command prints the system baseline, one HTTP result row, and suite details with throughput, operation count, errors, p50, p95, p99, and elapsed time. Add `--json` when a comparison pipeline needs structured output. The command runs the same App-owned suite as Lighthouse; it does not make a production target safer.
+
+## Compare One Change
+
+Change one material variable at a time: release, Driver, backend location, pool size, payload, or concurrency. Keep the remaining conditions stable and repeat enough runs to distinguish a durable change from environmental noise.
+
+Use a concurrency sweep to explore where throughput stops improving or errors and tail latency begin to rise. A sweep is still a synthetic comparison: it does not model production request mixes, think time, cache warmth, or contention from unrelated workloads.
+
+## Interpret the Report
+
+Read the result as one evidence bundle:
+
+- `ops/sec` is achieved throughput for that suite, target, and configuration—not an App-wide total, capacity guarantee, or SLO.
+- p50, p95, and p99 describe the observed successful timing distribution. Read them with operation count and errors; a low percentile does not excuse failed work.
+- elapsed time can differ from configured duration because setup, drain, cleanup, or backend behavior belongs to the run.
+- Driver and instance identity matter. Results from two differently located backends are not interchangeable even when their logical resource name matches.
+- the system baseline matters. CPU topology, memory, virtualization, co-located work, and network distance can dominate a small code change.
+
+Do not add operations-per-second values from different targets and call the sum application throughput. The suites run distinct operations against distinct resources.
+
+## Confirm with Production-Shaped Evidence
+
+Use Lighthouse benchmarks to form or reject a focused hypothesis. Before changing capacity, timeouts, or service objectives, confirm the conclusion with production-shaped load testing and the App's ordinary metrics, logs, readiness, and Inspects.
+
+After a production-authorized run, inspect the selected backend for benchmark records or queued work that cleanup could not remove.
+
+## Related
+
+- [Lighthouse](/operations/lighthouse)
+- [Metrics](/operations/metrics)
+- [Inspects](/operations/inspects)
+- [Queue Workers](/operations/queue-workers)
+- [Environment Reference](/reference/env-vars#demo-app)
diff --git a/docs/operations/runtime-processes.md b/docs/operations/runtime-processes.md
index ae1e256..1449d33 100644
--- a/docs/operations/runtime-processes.md
+++ b/docs/operations/runtime-processes.md
@@ -69,7 +69,7 @@ Common variables:
```text
APP_SHUTDOWN_TIMEOUT=30s
QUEUE_SHUTDOWN_TIMEOUT=10s
-SCHEDULER_SUBPROCESS_SHUTDOWN_TIMEOUT=90s
+SCHEDULER_SUBPROCESS_SHUTDOWN_TIMEOUT=30s
```
Constructors should build dependencies, not start long-running work. Keep business behavior independent of process topology, allow workers time to finish in-flight jobs, and use locking or singleton control before running multiple schedulers. Splitting processes does not provide shared state or job correctness by itself.
diff --git a/docs/operations/scheduler-processes.md b/docs/operations/scheduler-processes.md
index 787f8de..22b0fb0 100644
--- a/docs/operations/scheduler-processes.md
+++ b/docs/operations/scheduler-processes.md
@@ -64,10 +64,10 @@ Scheduler-owned command tasks have separate child-process controls:
```text
SCHEDULER_COMMAND_TIMEOUT=10m
-SCHEDULER_SUBPROCESS_SHUTDOWN_TIMEOUT=90s
+SCHEDULER_SUBPROCESS_SHUTDOWN_TIMEOUT=30s
```
-`SCHEDULER_COMMAND_TIMEOUT` bounds one command task. `SCHEDULER_SUBPROCESS_SHUTDOWN_TIMEOUT` gives an App command launched with scheduler-command origin its own graceful shutdown budget; it does not replace the parent scheduler Runtime's `APP_SHUTDOWN_TIMEOUT`.
+`SCHEDULER_COMMAND_TIMEOUT` bounds one command task. `SCHEDULER_SUBPROCESS_SHUTDOWN_TIMEOUT` gives an App command launched with scheduler-command origin its own graceful shutdown budget, but it cannot extend the remaining parent `APP_SHUTDOWN_TIMEOUT`. Leave it empty to inherit the App budget.
## Verify a Deployment
diff --git a/docs/public/design-system.css b/docs/public/design-system.css
index f500244..fdaa91b 100644
--- a/docs/public/design-system.css
+++ b/docs/public/design-system.css
@@ -7358,3 +7358,15 @@ html:not(.dark) .gf-blog-visual {
--gf-ink: #FFFFFF;
--gf-ink-2: #A9A1B3;
}
+/* Atlas uses its source-owned product banner while the framework guide remains the preferred user path. */
+.vp-doc .gf-atlas-banner {
+ margin: 1.5rem 0 1.75rem;
+}
+
+.vp-doc .gf-atlas-banner img {
+ display: block;
+ width: 100%;
+ border: 1px solid var(--vp-c-divider);
+ border-radius: 16px;
+ box-shadow: 0 18px 48px rgb(0 0 0 / 18%);
+}
diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md
index 7da86fc..4fc0641 100644
--- a/docs/reference/configuration.md
+++ b/docs/reference/configuration.md
@@ -10,7 +10,7 @@ GoForj has two main configuration layers:
- `.goforj.yml` for project rendering and development workflow.
- environment variables for runtime behavior.
-This page defines accepted keys and values. Start with [Configuration](/getting-started/configuration) for the first working change, or [forj dev](/developer-tools/forj-dev) for the build, SPA, and runtime loop. The lifecycle examples below illustrate configuration shapes rather than a second development tutorial.
+This page is a lookup for accepted keys and values. It does not replace the task-oriented setup guides or define production runtime policy. Start with [Configuration](/getting-started/configuration) for the first working change, or [forj dev](/developer-tools/forj-dev) for the build, SPA, and runtime loop. The lifecycle examples below illustrate configuration shapes rather than a second development tutorial.
## `.goforj.yml`
@@ -70,7 +70,17 @@ Compiled defaults fill values that remain unset after normal file-backed loading
Compiled overrides take precedence over process and file-backed values. A compiled `APP_ENV` override selects its matching environment file and remains authoritative after loading.
-Use these options for controlled packaging workflows. Prefer environment files and process environment for normal local development. See [CLI Reference](/reference/cli#framework-command-options) for the complete `forj build` option list.
+These values become part of the artifact contract:
+
+| Build input | Runtime can replace it? | Appropriate use |
+| --- | --- | --- |
+| `--env-defaults KEY=value` | Yes, with a process or file-backed value | A non-secret fallback that should travel with this artifact. |
+| `--env-overrides KEY=value` | No; rebuild the artifact to change it | A non-secret packaging constraint that must remain fixed for every process using this artifact. |
+| Process environment or deployment configuration | Yes, when the deployment changes it | Environment-specific endpoints, credentials, ports, scaling, retention, and operational policy. |
+
+Do not compile secrets into either channel. Compiled values can be recovered from or observed with the artifact, and an override prevents the deployment platform from correcting that key at startup. Treat a change to a compiled default or override like any other artifact change: rebuild, identify, test, and promote the new binary or image.
+
+Most production configuration remains deployment-owned. Use the deployment platform's environment, configuration, and secret delivery mechanisms for values that differ by environment or must rotate independently of a build. Prefer environment files and process environment for normal local development. See [CLI Reference](/reference/cli#framework-command-options) for the complete `forj build` option list and [Deploy an App](/operations/deployment-basics#keep-configuration-outside-the-artifact) for the production handoff.
## Development Tasks
@@ -334,6 +344,8 @@ Catalog dependencies are resolved in memory by the renderer. For example, metric
## Module Replaces
+`render.module_replaces` manages local Go module replacements during Project rendering. On render, GoForj applies each entry to `go.mod` with `go mod edit -replace` and records which module paths it owns in `.goforj.module_replaces.json`. When an owned entry is later removed from `.goforj.yml`, the next render drops that replacement without touching unrelated replacements that a maintainer added directly to `go.mod`.
+
Use paths that are stable from the Project root. For local sibling repositories, prefer a relative path:
```yaml
@@ -342,7 +354,7 @@ render:
github.com/goforj/web: ../web
```
-Do not use container-specific absolute paths; they only work in one local environment.
+This is a development and rendering aid, not runtime dependency configuration. Do not use container-specific or machine-specific absolute paths in shared Project configuration; they only work in one local environment. Before a release build, confirm that `go.mod` does not resolve production dependencies through unintended local replacements.
## Related Pages
diff --git a/docs/reference/env-vars.md b/docs/reference/env-vars.md
index ef67c13..f880eea 100644
--- a/docs/reference/env-vars.md
+++ b/docs/reference/env-vars.md
@@ -48,6 +48,7 @@ Variables for components that are not selected are not rendered and have no gene
| `APP_DIAG_TOKEN` | Generated when Web API is rendered | Bearer token for protected diagnostic commands and endpoints. |
| `APP_SHUTDOWN_TIMEOUT` | `30s` | Root graceful-shutdown budget. |
| `APP_VERSION` | Empty | Deployment version reported to Lighthouse. |
+| `APP_REVISION` | Empty | Immutable deployment revision used by framework-managed metrics discovery. Keep it bounded and low-cardinality, such as a commit SHA. |
| `APP_INSTANCE_ID` | Empty | Explicit process or replica identity reported to Lighthouse. When empty, Lighthouse identifies the instance by hostname, then its generated agent ID. |
| `APP_INSTANCE_KIND` | Empty | Optional deployment-specific instance classification reported to Lighthouse. |
| `APP_MODE` | Empty | Optional runtime mode used in generated log labels. Runtime commands normally set their own context. |
@@ -64,6 +65,8 @@ Variables for components that are not selected are not rendered and have no gene
| `APP_LOG_DEDUPE_WINDOW_MS` | `1200` | Dedupe window in milliseconds. |
| `APP_LOG_DEDUPE_BURST` | `2` | Matching messages emitted before suppression begins within a window. |
| `APP_LOG_DEDUPE_SUMMARY_EVERY` | `1000` | Suppressed occurrences between summary messages. |
+| `APP_LOG_REDACT_KEYS` | Empty | Comma-separated application-specific field names to redact in addition to mandatory secret-bearing names. |
+| `APP_LOG_REDACT_MESSAGE_PATTERNS` | Empty | JSON array of regular expressions replaced with `[REDACTED]` before output and sink delivery. Invalid JSON or expressions fail during logger construction. |
See [Logging](/operations/logging) for event shape, output modes, and sensitive-data guidance.
@@ -196,7 +199,7 @@ Framework-owned instrumentation toggles all default to `true` when their compone
| `METRICS_DATABASE_ENABLED` | Database operations. |
| `METRICS_AUTH_ENABLED` | Auth flows. |
| `METRICS_SCHEDULER_ENABLED` | Scheduler operations. |
-| `METRICS_MONITORING_ENABLED` | Monitoring metrics in the generated demo App. |
+| `METRICS_MONITORING_ENABLED` | Monitoring metrics in the demo App. |
See [Metrics](/operations/metrics) for endpoints, labels, and scrape topology.
@@ -249,7 +252,7 @@ The default connection uses `DB_`. Named connections use `DB___*` directly. |
-The renderer supplies usable local MySQL or Postgres connection values when those services are selected. Active driver values also accept the compatibility aliases `sqlite3`, `mariadb`, and `postgresql`; supported-driver lists and new configuration should use the canonical names above. See [Database Strategy](/data/database-strategy) and [Database Shell](/data/database-strategy#shell-options).
+The renderer supplies usable local MySQL or Postgres connection values when those services are selected. Active driver values also accept the compatibility aliases `sqlite3`, `mariadb`, and `postgresql`; supported-driver lists and new configuration should use the canonical names above. See [Database Connections](/data/database-strategy) and [Database Shell](/data/database-strategy#shell-options).
## Shared Redis and NATS
@@ -481,8 +484,8 @@ See [Mail](/applications/mail) for local delivery, named mailers, and production
| Variable | Default | Purpose |
| --- | --- | --- |
| `SCHEDULER_COMMAND_TIMEOUT` | `10m` | Maximum runtime for a command launched by a scheduled task. |
-| `SCHEDULER_SUBPROCESS_SHUTDOWN_TIMEOUT` | `APP_SHUTDOWN_TIMEOUT`; rendered as `90s` | Grace period for scheduler-owned subprocesses. |
-| `QUEUE_SHUTDOWN_TIMEOUT` | `10s` | Queue shutdown budget, also listed with Queue settings. |
+| `SCHEDULER_SUBPROCESS_SHUTDOWN_TIMEOUT` | `APP_SHUTDOWN_TIMEOUT` | Grace period for scheduler-owned subprocesses, capped by the remaining App shutdown budget. |
+| `QUEUE_SHUTDOWN_TIMEOUT` | `10s` | Queue shutdown budget, capped by the remaining App shutdown budget and also listed with Queue settings. |
## Local Observability
@@ -550,7 +553,7 @@ S3-backed `STORAGE__*` resources can be inventoried as backup inputs, but
See [Make Command Shared Options](/reference/make-commands#shared-options) and [forj dev](/developer-tools/forj-dev).
-## Generated Demo App
+## Demo App
These controls exist only when the demo monitoring and Lighthouse benchmark surfaces are rendered.
@@ -572,6 +575,6 @@ These controls exist only when the demo monitoring and Lighthouse benchmark surf
- [Configuration](/getting-started/configuration)
- [Driver Selection](/data/driver-selection)
-- [Generated Components](/core/code-generation)
+- [Code Generation](/core/code-generation)
- [Named Resources](/core/named-resources)
- [Production Hardening](/security/production-hardening)
diff --git a/docs/reference/errors.md b/docs/reference/errors.md
index ba1878a..c59fd1a 100644
--- a/docs/reference/errors.md
+++ b/docs/reference/errors.md
@@ -21,7 +21,7 @@ Likely causes:
Start with:
-- [Generated Components](/core/code-generation)
+- [Code Generation](/core/code-generation)
- [Generation Commands](/reference/generation-commands)
- [Configuration Reference](/reference/configuration)
@@ -40,7 +40,7 @@ Start with:
- [Providers](/core/dependency-injection#providers)
- [Wiring Recipes](/developer-tools/wiring-recipes)
- [Reading Wire Errors](/developer-tools/reading-wire-errors)
-- [Generated Files](/reference/generated-files)
+- [File Ownership](/reference/generated-files)
## Runtime Readiness Errors
diff --git a/docs/reference/generated-files.md b/docs/reference/generated-files.md
index 08aa356..1b356ff 100644
--- a/docs/reference/generated-files.md
+++ b/docs/reference/generated-files.md
@@ -1,60 +1,82 @@
---
-title: Generated Files
-description: Lookup reference for common generated files and ownership boundaries in GoForj Projects.
+title: File Ownership
+description: Identify which GoForj Project files belong to your application, the Framework, generators, or build tooling.
---
-# Generated Files
-
-Generated files are not all owned the same way.
-
-Check file headers and local package READMEs before editing.
-
-## Common Files and Directories
-
-| Path | Purpose |
-| --- | --- |
-| `.goforj.yml` | Project render and development contract. |
-| `.env` | Local runtime environment defaults. |
-| `cmd/app/main.go` | Default app binary entrypoint. |
-| `cmd//main.go` | Additional app binary entrypoint. |
-| `cmd//frontend/` | Frontend source and embedded build output for an app with Web UI. |
-| `app/commands.go` | Default app command exposure. |
-| `app/lifecycle.go` | Default app lifecycle hooks. |
-| `app/routes.go` | Default app route exposure. |
-| `app/schedules.go` | Default app schedule exposure. |
-| `app/wire/wire.go` | Default app Wire injector definitions. |
-| `app/wire/wire_gen.go` | Generated Wire output. Do not edit by hand. |
-| `app//...` | Additional app composition files. |
-| `app//wire/...` | Additional app Wire graph. |
-| `internal/runtime/apps.go` | App metadata derived from Project configuration and deterministic runtime defaults. Do not edit by hand. |
-| `internal/caches/*_gen.go` | Generated cache accessors and config when Cache is enabled. |
-| `internal/storages/*_gen.go` | Generated storage accessors and config when File Storage is enabled. |
-| `internal/queues/*_gen.go` | Generated queue accessors and config when Background Jobs is enabled. |
-| `internal/events/*_gen.go` | Generated event bus accessors and config when Events is enabled. |
-| `internal/database/*_gen.go` | Generated DB accessors and config when a database component is enabled. |
-| `build/api_index.json` | Default app API index output. |
-| `build/api_index.diagnostics.json` | Default app API index diagnostics. |
-| `build/openapi.json` | Default app OpenAPI output. |
-| `build/.webindex-artifacts.lock` | Publication lock coordinating the default app artifact set. |
-| `build//api_index.json` | Per-app API index output. |
-| `build//api_index.diagnostics.json` | Per-app API index diagnostics. |
-| `build//openapi.json` | Per-app OpenAPI output. |
-| `build//.webindex-artifacts.lock` | Publication lock coordinating one per-app artifact set. |
-| `.goforj/backups//manifest.json` | Local backup set inventory. |
-| `.goforj/backups//checksums.txt` | Checksums for backup artifacts. |
+# File Ownership
+
+Files in a GoForj Project have different owners. Some are normal application code, some are extension points created once, and some are refreshed from configuration or build inputs.
+
+Use this page when deciding whether to edit a file or change the input that creates it. File headers remain authoritative when a specific file says `DO NOT EDIT`.
+
+## Application and App Composition
+
+| Path | Owner | Created or refreshed by | Edit? |
+| --- | --- | --- | --- |
+| `internal//...` | Application | You and `forj make:*` | Yes |
+| `app/commands.go` | App | Initial render and make commands | Yes |
+| `app/lifecycle.go` | App | Initial render | Yes |
+| `app/routes.go` | App | Initial render and controller commands | Yes |
+| `app/schedules.go` | App | Initial render | Yes |
+| `app/wire/inject_*_app.go` | App | Initial render and make commands | Yes |
+| `app//...` | Additional App | Same conventions as the default App | Follow the matching default-App file |
+| `migrations/` | Application | `forj make:migration` and application changes | Yes |
+
+## Framework-Managed Project Files
+
+These files implement the selected Project configuration. Change `.goforj.yml`, component selection, or the owning framework template instead of treating them as durable extension points.
+
+| Path | Created or refreshed by | Edit? |
+| --- | --- | --- |
+| `cmd/app/main.go` | `forj render` | No; use App registration files |
+| `cmd//main.go` | `forj render` | No; use that App's registration files |
+| `app/root_cmd.go` | `forj render` | No |
+| `app/wire/app.go`, `app//wire/app.go` | `forj render` | No; add providers through `_app.go` files |
+| `app/wire/wire.go` | `forj render` | Avoid; compose custom sets through `_app.go` files |
+| `app/wire/inject_*.go` without `_app` | `forj render` | No |
+| `internal/runtime/apps.go` | `forj render` | No |
+
+## Generated Go Output
+
+These filenames are the concrete outputs readers will encounter; they are not wildcard categories.
+
+| Capability | Paths | Refreshed by | Edit? |
+| --- | --- | --- | --- |
+| Wire | `app/wire/wire_gen.go`, `app//wire/wire_gen.go` | `forj build` or Wire generation | No |
+| Cache | `internal/caches/manager_gen.go`, `internal/caches/accessors_gen.go` | Cache generation during `forj build` | No |
+| Storage | `internal/storages/manager_gen.go`, `internal/storages/accessors_gen.go` | Storage generation during `forj build` | No |
+| Queues | `internal/queues/manager_gen.go`, `internal/queues/accessors_gen.go` | Queue generation during `forj build` | No |
+| Events | `internal/events/manager_gen.go`, `internal/events/accessors_gen.go` | Event generation during `forj build` | No |
+| Mail | `internal/mail/manager_gen.go`, `internal/mail/accessors_gen.go` | Mail generation during `forj build` | No |
+| Database | `internal/database/connections_gen.go` | Database generation during `forj build` | No |
+
+## Build and Operational Output
+
+| Path | Purpose | Owner |
+| --- | --- | --- |
+| `build/api_index.json` | Default App API index | Build tooling |
+| `build/api_index.diagnostics.json` | Default App indexing diagnostics | Build tooling |
+| `build/openapi.json` | Default App OpenAPI document | Build tooling |
+| `build/.webindex-artifacts.lock` | Coordinates publication of the default App artifact set | Build tooling |
+| `build//...` | Equivalent artifacts for an additional App | Build tooling |
+| `bin/app`, `bin/` | Compiled App binaries | GoForj build pipeline |
+| `cmd//frontend/dist/` | Built frontend embedded by a Web UI App | SPA build tooling |
+| `.goforj/backups//manifest.json` | Backup set inventory | Backup tooling and operators |
+| `.goforj/backups//checksums.txt` | Backup artifact checksums | Backup tooling and operators |
+
+Project inputs such as `.goforj.yml`, `.env`, and `go.mod` are configuration rather than generated output. See [Configuration Reference](/reference/configuration) and [Environment Reference](/reference/env-vars) for their separate render, build, and restart boundaries.
## Ownership Rules
-- Files marked `DO NOT EDIT` should be regenerated.
-- Render-once files are App-owned extension points.
-- `internal/` owns behavior; `app/` owns exposure.
-- Framework-wide changes belong in GoForj templates or generators, not only in a rendered Project.
-- API artifacts and publication locks are tool-owned. Backup sets are operator-owned data and must not be committed.
+- Edit application behavior and App-owned `_app.go` extension points normally.
+- Change inputs and regenerate files marked `DO NOT EDIT`.
+- Keep Framework-wide fixes in GoForj templates or generators, not only in one rendered Project.
+- Do not commit build output, publication locks, or operator backup sets unless a repository explicitly owns a checked artifact.
## Related Pages
- [Apps](/core/apps)
-- [Generated Components](/core/code-generation)
-- [Generated Extension Points](/core/code-generation#choose-a-safe-extension-point)
+- [Project Structure](/getting-started/project-structure)
+- [App Extension Points](/core/code-generation#choose-a-safe-extension-point)
- [Code Generation](/core/code-generation)
- [Backup and Restore](/operations/backups)
diff --git a/docs/reference/generation-commands.md b/docs/reference/generation-commands.md
index 1c88a85..4ba6ce3 100644
--- a/docs/reference/generation-commands.md
+++ b/docs/reference/generation-commands.md
@@ -81,6 +81,6 @@ Use `forj build` when unsure.
## Related Pages
- [Code Generation](/core/code-generation)
-- [Generated Components](/core/code-generation)
+- [Code Generation](/core/code-generation)
- [Make Command Reference](/reference/make-commands)
- [Rendered App Smoke Tests](/testing/rendered-app-smoke-tests)
diff --git a/docs/reference/index.md b/docs/reference/index.md
index 33fae17..53c5b50 100644
--- a/docs/reference/index.md
+++ b/docs/reference/index.md
@@ -14,7 +14,7 @@ Use reference pages after you know the workflow and need exact command names, en
- [CLI Reference](/reference/cli)
- [Environment Reference](/reference/env-vars)
- [Configuration Reference](/reference/configuration)
-- [Generated Files](/reference/generated-files)
+- [File Ownership](/reference/generated-files)
- [Generation Commands](/reference/generation-commands)
- [Errors](/reference/errors)
diff --git a/docs/reference/make-commands.md b/docs/reference/make-commands.md
index fcb92ed..929d58f 100644
--- a/docs/reference/make-commands.md
+++ b/docs/reference/make-commands.md
@@ -342,19 +342,14 @@ func NewSyncReportsJob(queues *queues.Manager) *SyncReportsJob {
return &SyncReportsJob{queues: queues}
}
-// Queue creates a task and dispatches it to the selected queue.
-// Add application inputs as arguments when defining the payload contract.
-func (t *SyncReportsJob) Queue(ctx context.Context, name string) error {
- var p SyncReportsJobPayload
- // add your payload fields here
- // p.User = name
-
- payload, err := json.Marshal(p)
+// Queue dispatches the typed payload to the selected queue.
+func (t *SyncReportsJob) Queue(ctx context.Context, payload SyncReportsJobPayload) error {
+ data, err := json.Marshal(payload)
if err != nil {
return err
}
_, err = t.queues.WithContext(ctx).Dispatch(
- queue.NewJob(SyncReportsJobTypeName).Payload(payload).OnQueue("billing"),
+ queue.NewJob(SyncReportsJobTypeName).Payload(data).OnQueue("billing"),
)
return err
}
@@ -856,7 +851,7 @@ Generate a model and repository helpers in an explicit package.
forj make:model invoices --package billing
```
-The generator inspects the existing `invoices` table through the default database connection, so that connection must be available. Models use `--package` rather than `-d` because their placement follows database table ownership.
+The positional argument is the exact name of an existing table. The generator inspects `invoices` through the default database connection, so that connection must be available. It does not create or migrate the table, and `make:model` does not select a named connection. The generated Go type and filename are singularized from the inspected table name; when an exact table is missing, the command may suggest an existing singular or plural variant. Models use `--package` rather than `-d` because their placement follows database table ownership.
```bash
forj make:model invoices --package billing --remove
diff --git a/docs/scenarios/cached-user-profile.md b/docs/scenarios/cached-user-profile.md
index 1d9e429..ca165b9 100644
--- a/docs/scenarios/cached-user-profile.md
+++ b/docs/scenarios/cached-user-profile.md
@@ -383,7 +383,9 @@ forj route:list
Expected output includes:
-- `/api/v1/users/:id`
+```text
+/api/v1/users/:id
+```
## Try the Route
diff --git a/docs/scenarios/file-upload-storage.md b/docs/scenarios/file-upload-storage.md
index fecdd92..9264e44 100644
--- a/docs/scenarios/file-upload-storage.md
+++ b/docs/scenarios/file-upload-storage.md
@@ -471,7 +471,9 @@ forj route:list
Expected output includes:
-- `/api/v1/uploads`
+```text
+/api/v1/uploads
+```
## Try the Route
diff --git a/docs/scenarios/json-api-route.md b/docs/scenarios/json-api-route.md
index 074f9ea..6b7a783 100644
--- a/docs/scenarios/json-api-route.md
+++ b/docs/scenarios/json-api-route.md
@@ -11,7 +11,7 @@ This page is generated from an executable spec. An automated suite renders a fre
Scenario 1 of 7 in the [verified path](/scenarios/). Plan on about 15 minutes.
-This scenario adds a `GET /api/v1/users/:id` endpoint to a generated GoForj App.
+This scenario adds a `GET /api/v1/users/:id` endpoint to a GoForj App.
The endpoint is intentionally small. It establishes the normal shape for application features: start from the make command, keep the controller thin, put behavior behind a service, register providers explicitly, and verify the route through the generated runtime.
@@ -25,7 +25,7 @@ The endpoint is intentionally small. It establishes the normal shape for applica
## Prerequisites
-Start from a generated GoForj App with HTTP enabled.
+Start from a GoForj App with HTTP enabled.
## Golden Path State
@@ -258,7 +258,9 @@ forj route:list
Expected output includes:
-- `/api/v1/users/:id`
+```text
+/api/v1/users/:id
+```
## Try the Route
diff --git a/docs/scenarios/reports-daily-schedule.md b/docs/scenarios/reports-daily-schedule.md
index 04687a7..7dc4833 100644
--- a/docs/scenarios/reports-daily-schedule.md
+++ b/docs/scenarios/reports-daily-schedule.md
@@ -18,7 +18,8 @@ The schedule decides when daily report work should begin. The queue still owns e
## What You Will Build
- `internal/reports/daily.go` selects users that need daily reports.
-- `app/schedules.go` registers a named `reports:daily` schedule.
+- `internal/reports/daily_schedule.go` keeps the schedule's name, interval, and handler together.
+- `forj make:schedule` adds the schedule constructor to the App schedule collection without replacing `app/schedules.go`.
- The schedule calls a domain-owned method instead of putting report logic in scheduler bootstrap.
- The method dispatches `reports:generate` jobs, so workers continue to process report generation.
@@ -69,7 +70,8 @@ internal/users/repository.go
**Scheduler**
```text
-app/schedules.go
+internal/reports/daily_schedule.go
+app/wire/inject_schedules_app.go
```
**App wiring**
@@ -186,64 +188,56 @@ func (r *MemoryUserRepository) ListDailyReportTargets(_ context.Context) ([]repo
}
```
-## Step 4: Import Reports into the Schedule Registry
+## Step 4: Scaffold the Daily Schedule
-Add the daily runner package to the app-owned schedule registry.
+Use the App-owned generator so the schedule enters the existing `AppSchedules` collection without replacing the App registry or any schedules already registered there.
-Update `app/schedules.go` so it includes:
-
-```go
-"your/module/internal/reports"
-"your/module/internal/schedules"
-```
-
-## Step 5: Add Schedule Registry Field
-
-Store the injected runner on the app schedule registry.
-
-Update `app/schedules.go` so it includes:
-
-```go
-type ScheduleRegistry struct {
- dailyReports *reports.DailyRunner
+```bash
+forj make:schedule reports:daily --every 24h --no-open
```
-## Step 6: Add Schedule Registry Constructor Parameter
+## Step 5: Connect the Schedule to the Runner
-Wire can now provide the runner to the app schedule registry.
+Replace the new App-owned schedule scaffold with its real dependency and handler. Its constructor is already present in `app/wire/inject_schedules_app.go`, so Wire supplies `DailyRunner` when it builds the schedule collection.
-Update `app/schedules.go` so it includes:
+Create or replace `internal/reports/daily_schedule.go`:
```go
-func NewScheduleRegistry(
- dailyReports *reports.DailyRunner,
-```
-
-## Step 7: Assign Schedule Registry Runner
-
-Preserve generated schedule wiring and add the new field assignment.
+// Package reports owns report generation and its recurring dispatch boundary.
+package reports
-Update `app/schedules.go` so it includes:
+import (
+ "context"
+ "time"
+)
-```go
-return &ScheduleRegistry{
- dailyReports: dailyReports,
-```
+// DailySchedule dispatches the daily report workflow on its configured interval.
+type DailySchedule struct {
+ runner *DailyRunner
+}
-## Step 8: Register the Schedule
+// NewDailySchedule requires the workflow that this schedule triggers.
+func NewDailySchedule(runner *DailyRunner) *DailySchedule {
+ return &DailySchedule{runner: runner}
+}
-Keep the registry declarative. The registry names the schedule and points to the domain-owned method.
+// Name returns the operational schedule name.
+func (s *DailySchedule) Name() string {
+ return "reports:daily"
+}
-Update `app/schedules.go` so it includes:
+// Interval returns how often the schedule should run.
+func (s *DailySchedule) Interval() (time.Duration, error) {
+ return 24 * time.Hour, nil
+}
-```go
-func (r *ScheduleRegistry) Register(s *schedules.Scheduler) error {
- s.DailyAt("04:00").
- Name("reports:daily").
- Do(s.InspectTask("reports:daily", r.dailyReports.Run))
+// Handle dispatches eligible report work when the schedule is due.
+func (s *DailySchedule) Handle(ctx context.Context) error {
+ return s.runner.Run(ctx)
+}
```
-## Step 9: Wire the Runner
+## Step 6: Wire the Runner
The previous scenario already binds the report job to `ReportQueue`. Add the daily runner and bind the user repository to daily target lookup.
@@ -255,7 +249,7 @@ reports.NewDailyRunner,
wire.Bind(new(reports.DailyTargetRepository), new(*users.MemoryUserRepository)),
```
-## Step 10: Test the Runner
+## Step 7: Test the Runner
Create `internal/reports/daily_test.go`.
@@ -327,12 +321,12 @@ go test ./...
## Verify the Schedule
-For a fast local check, first edit `app/schedules.go` to use a short interval:
+For a fast local check, first edit `DailySchedule.Interval` in `internal/reports/daily_schedule.go` to use a short interval:
```go
-s.Every(30).Seconds().
- Name("reports:daily").
- Do(s.InspectTask("reports:daily", r.dailyReports.Run))
+func (s *DailySchedule) Interval() (time.Duration, error) {
+ return 30 * time.Second, nil
+}
```
Rebuild after changing the schedule:
@@ -347,7 +341,7 @@ With the default process-local `workerpool` driver, start the combined App so th
forj app
```
-After the check, restore `DailyAt("04:00")` in `app/schedules.go` and rebuild again:
+After the check, restore `24 * time.Hour` in `internal/reports/daily_schedule.go` and rebuild again:
```bash
forj build
diff --git a/docs/scenarios/reports-generate-job.md b/docs/scenarios/reports-generate-job.md
index 1174b59..52ccd7c 100644
--- a/docs/scenarios/reports-generate-job.md
+++ b/docs/scenarios/reports-generate-job.md
@@ -628,7 +628,9 @@ forj route:list
Expected output includes:
-- `/api/v1/users`
+```text
+/api/v1/users
+```
## Try the Route
diff --git a/docs/scenarios/runtime-observability.md b/docs/scenarios/runtime-observability.md
index f637965..9f36ad6 100644
--- a/docs/scenarios/runtime-observability.md
+++ b/docs/scenarios/runtime-observability.md
@@ -74,7 +74,9 @@ grep -Fx LIGHTHOUSE_INSPECT_ENABLED=true .env.local
Expected output includes:
-- `LIGHTHOUSE_INSPECT_ENABLED=true`
+```text
+LIGHTHOUSE_INSPECT_ENABLED=true
+```
```bash
forj route:list
@@ -82,8 +84,10 @@ forj route:list
Expected output includes:
-- `/api/v1/users`
-- `/metrics`
+```text
+/api/v1/users
+/metrics
+```
## Trigger the Workflow
diff --git a/docs/scenarios/users-created-event.md b/docs/scenarios/users-created-event.md
index 7ac381e..f4577f8 100644
--- a/docs/scenarios/users-created-event.md
+++ b/docs/scenarios/users-created-event.md
@@ -875,7 +875,9 @@ forj route:list
Expected output includes:
-- `/api/v1/users`
+```text
+/api/v1/users
+```
## Try the Route
diff --git a/docs/starter-kits.md b/docs/starter-kits.md
index c18938d..afd4542 100644
--- a/docs/starter-kits.md
+++ b/docs/starter-kits.md
@@ -46,7 +46,7 @@ noAutoTitle: true
Account flows with real states
Sign-in, registration, password reset, profile, password, and appearance screens are
- generated as app-owned source. Teams start from complete flows instead of blank forms.
+ created as App-owned source. Teams start from complete flows instead of blank forms.
diff --git a/docs/testing/index.md b/docs/testing/index.md
index b10e6da..bad5cc1 100644
--- a/docs/testing/index.md
+++ b/docs/testing/index.md
@@ -40,7 +40,7 @@ go test ./...
Expected result: each package reports `ok`; a failure identifies the package and test name to investigate.
-GoForj Apps include generated tests for enabled framework-owned surfaces such as lifecycle idempotency, runtime topology defaults, health and readiness, Swagger serving, metrics, events, database connections, and generated commands.
+GoForj Apps include generated tests for enabled framework-owned surfaces such as lifecycle idempotency, runtime topology defaults, health and readiness, OpenAPI serving, metrics, events, database connections, and generated commands.
## Keep Domain Behavior Direct
diff --git a/docs/testing/integration-tests.md b/docs/testing/integration-tests.md
index 64cd8c9..2f14056 100644
--- a/docs/testing/integration-tests.md
+++ b/docs/testing/integration-tests.md
@@ -54,5 +54,5 @@ Avoid depending on a developer's local `.env` unless the test is intentionally v
## Next Steps
- [Rendered App Smoke Tests](/testing/rendered-app-smoke-tests) covers template confidence.
-- [Database Strategy](/data/database-strategy) explains connection generation.
+- [Database Connections](/data/database-strategy) explains connection generation.
- [Testing](/testing/) explains how to choose a test layer.
diff --git a/docs/testing/rendered-app-smoke-tests.md b/docs/testing/rendered-app-smoke-tests.md
index 07a4e86..5f26e47 100644
--- a/docs/testing/rendered-app-smoke-tests.md
+++ b/docs/testing/rendered-app-smoke-tests.md
@@ -1,9 +1,9 @@
---
-title: Rendered App Smoke Tests
-description: How GoForj contributors validate templates and generated Apps through disposable rendered smoke tests.
+title: Rendered Project Smoke Tests
+description: How GoForj contributors validate templates and generators through disposable rendered Projects.
---
-# Rendered App Smoke Tests
+# Rendered Project Smoke Tests
Rendered App smoke tests validate that GoForj templates and generators produce a working App.
@@ -29,7 +29,7 @@ Rendered smoke tests catch:
- missing imports
- invalid generated accessors
- Wire generation failures
-- generated App compile failures
+- rendered output compile failures
- generated test failures
- dependency replacement issues
- multi-app wiring or binary-entrypoint regressions when the smoke target includes additional apps