10 ERROR-FLAG PIC X VALUE 'N'.
88 ERROR-FOUND VALUE 'Y'.
88 ERROR-NOT-FOUND VALUE 'N'.
The 10-level number defines one byte of storage, as alpha-numeric, which means without hint of a problem it can contain any bit-value from X'00' to X'FF'.
The two 88-levels define no storage.
An 88 is effectively a way of giving a name to a literal (or some multiple) literal value, but to have it associated only with the particular field that it references (ERROR-FLAG in this case).
Here ERROR-FLAG is the "conditional variable" (which just means it has one or more 88-levels associated with it) and each of the 88s is a "condition-name".
If you do this:
SET ERROR-NOT-FOUND TO TRUE
Then
IF ERROR-NOT-FOUND
Will be true, and
IF ERROR-FOUND
Will not be true.
The SET is the same as:
MOVE "N" TO ERROR-FLAG
And the IF is the same as:
IF ERROR-FLAG EQUAL TO "N" *> for the IF ERROR-NOT-FOUND
The advantages of the 88 and the SET to change the value of the field that it refers to are documentary, and a reduction in maintenance.
Remember, a condition-name defines no storage (strictly, there is storage associated with it, but it just contains a literal and you can't, validly, access it).
So SET ERROR-NOT-FOUND TO TRUE
(nice spacing, very nice) does nothing to ERROR-NOT-FOUND (there is nothing that can be done), but places the value "N" in ERROR-FLAG.
So SET ERROR-FOUND TO TRUE
does nothing to ERROR-FOUND, but places the value "Y" in ERROR-FLAG.
Coding both those SET statements in sequence simply ends up with ERROR-FLAG being "Y" (the first SET would be redundant).
The SET statement for an 88-level generates code identical to a MOVE statement (or should/could, don't know about all compilers). You use the SET so that you can't accidentally mess with the value of a flag through a typo (or through incompetence).
A better structure for flags/switches is:
01 FILLER.
10 FILLER PIC X.
88 ERROR-FOUND VALUE 'Y'.
88 ERROR-NOT-FOUND VALUE 'N'.
10 FILLER PIC X.
88 AMOUNT-NUMERIC VALUE 'Y'.
88 AMOUNT-NOT-NUMERIC VALUE 'N'.
When you give the conditional-variable a name, as you did in your example, someone can code a MOVE
to reference it, and typo the value, or make analysis of the code more difficult. Using FILLER to define the fields, which cannot be referenced from the PROCEDURE DIVISION, prevents that happening. The data defined can only be accessed through SET and IF (to interrogate the value).
The code would have:
SET ERROR-NOT-FOUND
AMOUNT-NOT-NUMERIC TO TRUE
To start of each processing iteration (best not to rely on VALUE, as you have to re-set each time anyway) and then individual SET statements when the particular symptoms requiring the flag are identified.
Not, 88's can have multiple values. VALUE "A" "Q" "V" THRU "Z", for example. When doing the SET, the value used is the one mentioned first on the VALUE statement, "A" in this example.
See this answer, with a number of links to other answers: https://stackoverflow.com/a/21338755/1927206