Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,31 @@ help: ##@Command Show help
# ======================================================================
# DOCKER
# ======================================================================
up: ##@docker Start the stack in foreground
up: ensure-vendor ##@docker Start the stack in foreground
$(DC) up

upd: ##@docker Start the stack in background
upd: ensure-vendor ##@docker Start the stack in background
$(DC) up -d

# The backend services bind-mount ./backend-symfony over /app, which shadows the
# vendor/ tree the image builds in. vendor/ is gitignored, so on a fresh clone
# there is nothing behind the mount and public/index.php dies on
# `Failed opening required '/app/vendor/autoload_runtime.php'` before it can
# answer /healthz. Install once, here, so `make upd` gives a working stack.
#
# Guarded on autoload_runtime.php: once vendor/ is populated this is a single
# `test -f` and the normal path costs nothing. The directories are created and
# chmodded first because the image runs as USER 10001, which cannot write into a
# root-owned or host-user-owned directory it did not create (Linux hosts).
# `--no-deps` keeps postgres and redis out of it: composer needs neither.
ensure-vendor: ##@docker Install PHP dependencies if vendor/ is missing (first run only)
@if [ ! -f backend-symfony/vendor/autoload_runtime.php ]; then \
echo "backend-symfony/vendor is empty (fresh clone) — installing PHP dependencies once..."; \
mkdir -p backend-symfony/vendor backend-symfony/var backend-symfony/var/cache backend-symfony/var/log backend-symfony/public/bundles; \
chmod -R 777 backend-symfony/vendor backend-symfony/var backend-symfony/public/bundles 2>/dev/null || true; \
$(DC) run --rm --no-deps $(PHP_CONTAINER_DEV) composer install --no-interaction --no-progress; \
fi

down: ##@docker Stop and remove containers
$(DC) down

Expand Down Expand Up @@ -483,7 +502,7 @@ testOne: ##@test Run a single integration/unit test (q=filter)
# .PHONY DECLARATION
# ======================================================================
.PHONY: help \
up upd down ps build log-backend log-db \
up upd ensure-vendor down ps build log-backend log-db \
composer composer-test composer-install composer-update composer-require composer-require-dev composer-remove \
cc cc-test console console-test debug-router \
migration migration-diff migration-generate create-database reset-db schema-create \
Expand Down
35 changes: 29 additions & 6 deletions docs/08_getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,13 @@ You should see the following services:
| `frontend` | 3002 | React frontend |
| `n8n` | 5678 | Workflow automation |

Verify the backend is responding:

```bash
curl -s http://localhost:8081/healthz
# Expected: {"status":"ok"}
```
> **Don't curl the backend yet.** The containers are up, but the backend has no
> PHP dependencies until step 3. The `backend-dev` service bind-mounts
> `./backend-symfony` over `/app` so your local edits are live without a rebuild,
> and that mount hides the `vendor/` directory the image was built with. `vendor/`
> is gitignored, so on a fresh clone there is nothing behind the mount.
> `make up` / `make upd` install it for you the first time (see `make ensure-vendor`),
> which is why the first start takes a few minutes. Verify the API in step 3.

---

Expand All @@ -107,6 +108,13 @@ make migration
make fixtures-dev
```

Now that `vendor/` is populated, verify the backend is responding:

```bash
curl -s http://localhost:8081/healthz
# Expected: {"status":"ok"}
```

> **What `make migration` does**: executes all Doctrine migrations to create the schema (tables, indexes, foreign keys, views).

> **What `make fixtures-dev` does**: seeds the development database with reference data (14 scam types, 27 personas across 7 archetypes, lookup tables for channels and directions) **and creates two default users** (see below).
Expand Down Expand Up @@ -632,6 +640,21 @@ make build
make up
```

### `Failed opening required '/app/vendor/autoload_runtime.php'`

The backend container is running but every request returns this PHP fatal error.
`backend-symfony/vendor/` is empty: it is gitignored, and the `./backend-symfony:/app`
bind-mount hides the copy baked into the image. Install the dependencies into your
working copy:

```bash
make composer-install
```

`make up` / `make upd` do this automatically on a fresh clone, and `make doctor`
reports it explicitly. Do not remove the bind-mount — it is what makes local edits
visible without rebuilding the image.

### "Cannot find the redis extension" error

This means the Docker image was built without the Redis PHP extension. Rebuild:
Expand Down
1 change: 1 addition & 0 deletions factory/found-issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ surveying the repo is recorded here for you to triage separately.
| 6 | 4 | low | `backend-symfony/tests/**` (8 files, 50 `setValue` calls) | **Reflection-based entity mutation in tests.** `ReflectionProperty::setValue` is used to age a `Conversation` (`tsLast`), force a `status`, set `rewardValue`, or swap a private collaborator — e.g. `tests/Integration/Command/CloseStaleConversationsCommandTest.php`, `CalculateRewardsCommandTest.php`, `tests/Unit/Application/LLM/ReplyPipelineWiringTest.php`. Each one marks a state the domain cannot express through its own API. The constitution bans this in new code; these are inherited and are not blocked, because the Semgrep gate scans the diff against the merge base. |
| 8 | 4 | medium | `backend-symfony/src/Application/Meta/PreprodCopyService.php:15` | **Database credentials hardcoded in source.** `private const PREPROD_DSN = 'postgresql://scambuster:postgres@postgres-preprod:5432/scambuster_preprod';`. Found by the new Semgrep rule, not by Gitleaks — `.gitleaks.toml` looks for secret *shapes* and a weak internal password in a DSN does not match one. It is a preprod host with a throwaway password, so the impact is low, but it is a credential in a public repository and it will be copied the next time someone needs a DSN. Should be read from the environment. **Registered as `SEC-001` in `factory/security-findings.md`** — it is a security finding, so it gets fixed through its own `/factory-security` run, not here. |
| 7 | 4 | informational | `backend-symfony/src/Domain/**` (24 of 73 files) | **Domain entities are Doctrine-annotated.** They import `Doctrine\ORM\Mapping`, `Common\Collections` and `DBAL\Types\Types`. This is not a defect — it is this codebase's deliberate and dominant pattern — but it means the architecture is DDD-with-annotated-entities, not strict hexagonal. Recorded because the Phase 1 constitution originally claimed the Domain layer had no Doctrine dependency at all, which was false. What *is* true, and now enforced: the Domain never reaches persistence at runtime. |
| 9 | bug pipeline | medium | `Makefile`, `scripts/**`, `docker-compose.yml` | **No pipeline can carry a defect in the build/onboarding tooling.** Found while running `/factory-bug` on the fresh-clone `vendor/` failure. The bug pipeline requires a reproduction test committed before the fix, and the constitution requires that test to be seen red. Neither is possible for this class of defect: the only automated suites are PHPUnit (`make test` runs `vendor/bin/phpunit`, `Makefile:218`) and Vitest, both of which execute *inside* a container whose `vendor/` must already exist — the exact state the bug removes. A PHPUnit test asserting Makefile content would run only where the bug is absent and could never go red in CI. The repository has no shell/Make test harness and no CI job that would run one (`scripts/test-llm/*.sh` are manual probes). Consequence: onboarding and tooling defects can only ship as `chore`, which is gated by ordinary CI and no test at all. Deciding where such a harness lives, what runs it, and whether it blocks is a design decision — a `/factory-feature` run, not a bug fix. |
17 changes: 17 additions & 0 deletions scripts/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,23 @@ check_var "INGEST_LOGIN" "user@example.com"

echo ""

# ─── BACKEND DEPENDENCIES ───
# The backend services bind-mount ./backend-symfony over /app, which shadows the
# vendor/ tree baked into the image. vendor/ is gitignored, so on a fresh clone
# there is nothing behind the mount and public/index.php fails on
# `Failed opening required '/app/vendor/autoload_runtime.php'`. Checked before
# connectivity, because that is what a missing vendor/ looks like from below:
# the container is up and every port answers, but /healthz returns a PHP fatal.
echo "BACKEND DEPENDENCIES"

if [ -f backend-symfony/vendor/autoload_runtime.php ]; then
ok "PHP dependencies — backend-symfony/vendor installed"
else
fail "PHP dependencies — backend-symfony/vendor/autoload_runtime.php missing. Run 'make composer-install' (or 'make upd', which installs it on first run)."
fi

echo ""

# ─── CONNECTIVITY ───
echo "CONNECTIVITY"

Expand Down
Loading