feat(fls): write the system software of a .fls, loaded by Java as the original

topologyWriter.ts now sets the system software each device's constructor
makes, through its setters: a switch's SSID and learning time, a host's
SSID, RIP, manual routes, DHCP client and server with static assignments,
static NAT, the modem, and a router's or home router's firewall. The
firewall is reached as XMLEncoder reaches it, through the web server's
`konfig` plug-in: the constructor hangs the same object there and among
the applications, where its key differs (Firewall on a router, NatGateway
on a home router). A switch's SSID the file lacked stays unwritten, so Java
rolls one as it did. Files and applications are later layers.

javaBeansWriter.ts: a call can carry statements on its result (a map's
value changed through get(key)), checked by Java's XMLDecoder in a new
case; a short() helper for Java's short values.

Checked by Java against Java: every corpus file, read and written again,
loads with the same system software (without files and applications) as
the original. The synthetic topology adds what the corpus lacks: a router
firewall switched on with rules of every protocol (one with a null
address), every flag away from its default, a modem with its own port and
target, and a PC's SSID, routes and DHCP server. 16 mutation probes, each
rewritten and loaded by Java, were all caught.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 08:18:52 +00:00
co-authored by Claude Opus 5.5
parent 314f696515
commit facad5843e
8 changed files with 749 additions and 227 deletions
+10 -4
View File
@@ -59,14 +59,15 @@ export type BeanStatement =
| { readonly set: string; readonly value: BeanValue }
/** target.getP(), then the statements on its result; `as` names the result */
| { readonly get: string; readonly statements: readonly BeanStatement[]; readonly as?: BeanReference }
/** target.m(args) */
| { readonly call: string; readonly args: readonly BeanValue[] }
/** target.m(args), then the statements on its result */
| { readonly call: string; readonly args: readonly BeanValue[]; readonly statements?: readonly BeanStatement[] }
/** target.set(index, value) on a list */
| { readonly setIndex: number; readonly value: BeanValue }
/** target.get(index), then the statements on its result; `as` names the result */
| { readonly getIndex: number; readonly statements: readonly BeanStatement[]; readonly as?: BeanReference };
export const int = (value: number): BeanPrimitive => ({ kind: 'int', value });
export const short = (value: number): BeanPrimitive => ({ kind: 'short', value });
export const long = (value: number | bigint): BeanPrimitive => ({ kind: 'long', value });
export const double = (value: number): BeanPrimitive => ({ kind: 'double', value });
export const float = (value: number): BeanPrimitive => ({ kind: 'float', value });
@@ -194,12 +195,14 @@ class Writer {
} else if ('get' in statement) {
this.getter(`property="${escapeAttribute(statement.get)}"`, statement.statements, statement.as, depth, lines);
} else if ('call' in statement) {
if (statement.args.length === 0) {
const inner = statement.statements ?? [];
if (statement.args.length === 0 && inner.length === 0) {
lines.push(`${indent}<void method="${escapeAttribute(statement.call)}"/>`);
return;
}
nested(`<void method="${escapeAttribute(statement.call)}"`, () => {
for (const arg of statement.args) this.value(arg, depth + 1, lines);
for (const each of inner) this.statement(each, depth + 1, lines);
});
} else if ('setIndex' in statement) {
nested(`<void index="${statement.setIndex}"`, () => this.value(statement.value, depth + 1, lines));
@@ -252,7 +255,10 @@ function sharedObjects(roots: readonly BeanValue[]): Set<BeanObject | BeanArray>
const shared = new Set<BeanObject | BeanArray>();
const visitStatement = (statement: BeanStatement): void => {
if ('set' in statement || 'setIndex' in statement) visit(statement.value);
else if ('call' in statement) statement.args.forEach(visit);
else if ('call' in statement) {
statement.args.forEach(visit);
(statement.statements ?? []).forEach(visitStatement);
}
else statement.statements.forEach(visitStatement);
};
const visit = (value: BeanValue): void => {