test: prove internal package visibility

This commit is contained in:
2026-08-13 15:38:03 +09:00
parent 9a5dbac84f
commit 088105413d
2 changed files with 756 additions and 0 deletions

View File

@@ -3855,6 +3855,139 @@ preloader, parallel action scheduler, or network behavior. Normal local WW
builds and tests remain offline, manifest-free, registry-free, database-free,
CAS-free, and network-free.
### 11.15 Implemented internal-package import visibility
A source import whose complete canonical import path contains a directory
component named exactly `internal` is now contextual. Locate the final such
component. Its parent directory is the ownership boundary, and the import is
legal only when the importing source package's canonical filesystem directory
is that boundary or a descendant at a real path-component boundary. Thus
`domain.client` may import `domain.internal.secret`, while `outsider` and
`domainx.client` may not. `internalx` has no special meaning. For
`domain.internal.outer.internal.deep`, the final `internal` wins and the owner
is `domain.internal.outer`, not `domain`.
The complete dotted import identity determines whether the rule applies and
how many components comprise the final `internal` plus its following suffix.
WW removes those components from that source edge's resolved lexical target
route, canonicalizes the resulting owner directory physically, and compares it
with the already canonical physical importer directory. Stripping precedes
physical canonicalization because a symlink at or below `internal` may point to
a target with a different name or depth; an action spelling interned by an
earlier edge is never reused for this contextual calculation. Cstage obtains
directory canonicalization through `realpath`; WWstage uses its equivalent
`chdir`/`getcwd` canonicalization. For explicit single-file compatibility
roots, Cstage uses `realpath` and WWstage's component walker uses
`lstat`/`readlink` plus the same canonical current-directory representation;
both preserve component and trailing-directory semantics across symlinks. The
equality-or-`/`-boundary comparison never uses a raw string prefix. This
division is intentional: an unrelated physical
ancestor literally named `internal` does not impose visibility on an import
whose canonical identity has no such component, while a symlink spelling at or
below an import-path `internal` cannot move the effective owner or importer.
Explicit single-file compatibility roots use their canonicalized containing
directory as the importer context. Directly selecting an internal directory as
a command root remains legal because selection is not a source import.
Visibility is an import-edge property, not part of canonical package or action
identity. Both drivers resolve the complete target, intern or reuse its one
canonical production action, and then perform the importer-context check before
accepting the source binding or dependency edge. Consequently an allowed
importer may load and compile the target normally, but that cached action cannot
authorize a later forbidden importer. Reversing requested products or visiting
the forbidden importer first produces the same result. No declared package
name, leaf, output name, product ordinal, storage locator, hash, or test variant
participates in the decision. Legal edges therefore retain the existing
owner-only byte-sorted unit, exact sorted/deduplicated direct `.wwi` compiler
inputs, deterministic archive, and complete archive-only linker closure.
Cstage represents the rule with the bounded
`sep_internal_parent_count`/`sep_internal_import_allowed` helpers in
`cmd/ww/main.c`. WWstage has the isomorphic
`sepinternalparentcount`/`sepinternalimportallowed` helpers in
`selfhost/cmd/ww/main.ww`. Neither adds a package field, fixed-size package
table, second action universe, or action-key input. A distinct
`SEP_LOAD_INTERNAL` result propagates through the iterative loader. On rejection
both stages emit the importing parser position followed by exactly:
```text
use of internal package <canonical-import-path> not allowed
```
The command returns immediately from graph loading, before directory-identity
finalization, unit composition, workdir owner validation, stale-voucher
invalidation, tool-identity recording, compiler, assembler, in-driver archive
production, linker, publication, or execution. A cold rejection therefore
commits no target/importer artifact or `.wwtool.*` state. A forbidden warm
request cannot use a previously committed target to bypass the check and leaves
the already committed target voucher and all tool records byte-unchanged.
Allocation or path canonicalization failure remains a deterministic
command-fatal pre-tool error; the visibility helper does not change stable
package indices or growth behavior.
Every selected source file reaches the same driver scan. The rule therefore
applies to ordinary production sources, same-package test sources in the
internal production-plus-test variant, external `_test` sources, and real
source imports inside the test-support package. Generated-main-to-variant,
generated-main-to-support, and coordinator product wiring remain synthetic
edges and are not retroactively treated as source imports. No coordinator
change is required: `cmd/wwtest` only dispatches, and
`internal/wwpackage/package.ww` only discovers and classifies source groups,
constructs the union request, and runs the driver; neither resolves a source
import or owns its importing position.
This follows only the pinned official Go 1.26.5 source at commit
`c19862e5f8415b4f24b189d065ed739517c548ba`:
- `loadImport` resolves and reuses the canonical package first, then explicitly
checks the rule on every import because the result depends on the importing
code, and attaches the importing position
([`pkg.go`, lines 787791](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L787-L791)).
- The rule is the tree rooted at the parent of the target's `internal`
directory
([`pkg.go`, lines 14631471](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L1463-L1471)).
A package named directly on the command line is not an import
([lines 14981502](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L1498-L1502)),
and the import-path boundary is located before filesystem containment
([lines 15051515](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L1505-L1515)).
- Go's filesystem branch cleans the importer and owner, requires a
path-component-aware prefix, and retries with both paths symlink-expanded
([`pkg.go`, lines 15341546](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L1534-L1546)).
Its rejection text is exactly the diagnostic above
([lines 15641571](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L1564-L1571)),
and its exact-component search deliberately selects the final `internal`
element as the most restrictive rule
([lines 15741590](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L1574-L1590)).
- Same-package and external test imports both pass through ordinary
`loadImport` with their own source positions before variant construction
([`test.go`, lines 102161](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/test.go#L102-L161)).
Generated test-main dependencies are synthesized separately
([lines 307330](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/test.go#L307-L330)).
- Go's downstream action cache keys canonical package actions independently
and consumes the already validated direct package imports, so visibility does
not belong in action identity
([`action.go`, lines 437455](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L437-L455),
[lines 628657](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L628-L657)).
The focused native observer `internal_package_import_visibility` generates its
lexical package/import tree under a temporary physical ancestor also named
`internal`, while deliberate symlink destinations live outside that ancestor,
so that unrelated-host-path case is exercised rather than documented only. It
builds and runs allowed descendant, nested-final, `internalx`, and symlinked
physical-owner cases; rejects outsider, sibling-prefix, nested-final, and
symlink-escape cases at exact source positions with empty tool traces; maps an
internal target symlinked to a differently shaped physical path back to its
lexical owner's canonical directory; builds an internal package directly; and
reverses requested allowed/forbidden product-descriptor order around one reused
target. It checks ordinary, internal-test, and external-test
source imports, generated-main transitive-export isolation, owner-only source
order, direct export inputs, archive-only link closure, runtime results, warm
no-op package production, rejection-state preservation, and the primary
production chain's unit, export, assembly, object, archive, binary, and exact
normalized tool arguments across independent cold Cstage and WWstage work
roots. Test variants additionally compare their unit/export/archive bytes and
exercise byte-equivalent runtime output in both stages.
## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins.